Title
§[range.utility.conv.to] ranges::to may cause infinite recursion if range_value_t<C> is a non-move-constructible range
Status
new
Section
[range.utility.conv.to]
Submitter
S. B. Tam

Created on 2023-11-08.00:00:00 last changed 7 months ago

Messages

Date: 2024-03-11.21:55:38

Proposed resolution:

This wording is relative to N4964.

  1. Modify [range.utility.conv.to] as indicated:

    template<class C, input_range R, class... Args> requires (!view<C>)
      constexpr C to(R&& r, Args&&... args);
    

    -1- Mandates: C is a cv-unqualified class type.

    -2- Returns: An object of type C constructed from the elements of r in the following manner:

    1. (2.1) — If C does not satisfy input_range or convertible_to<range_reference_t<R>, range_value_t<C>> is true:

      1. […]

    2. (2.2) — Otherwise, if same_as<range_reference_t<R>, range_value_t<C>> is false and input_range<range_reference_t<R>> is true:

      to<C>(r | views::transform([](auto&& elem) {
        return to<range_value_t<C>>(std::forward<decltype(elem)>(elem));
      }), std::forward<Args>(args)...);
      
    3. (2.3) — Otherwise, the program is ill-formed.

Date: 2024-03-15.00:00:00

[ 2024-03-11; Reflector poll ]

Set priority to 3 after reflector poll.

"Do we want same_as or !different-from?"

Date: 2023-11-08.00:00:00

[range.utility.conv.to]/2 says:

  1. (2.1) — If C does not satisfy input_range or convertible_to<range_reference_t<R>, range_value_t<C>> is true:

    1. […]

  2. (2.2) — Otherwise, if input_range<range_reference_t<R>> is true:

    to<C>(r | views::transform([](auto&& elem) {
      return to<range_value_t<C>>(std::forward<decltype(elem)>(elem));
    }), std::forward<Args>(args)...);
    
  3. […]

That is, if range_reference_t<R> is not convertible to range_value_t<C>, and range_reference_t<R> is an input range, views::transform is applied to convert the inner range to range_value_t<C> (through to<range_value_t<C>>), and then the transformed range is converted to C (through to<C>).

Consider:

#include <ranges>

struct ImmovableRange {
  ImmovableRange(int*, int*);
  ImmovableRange(ImmovableRange&&) = delete;

  int* begin();
  int* end();
};

struct C {
  ImmovableRange* begin();
  ImmovableRange* end();
};

using R = int[1][2];

void test() {
  (void)std::ranges::to<C>(R{});
}

Here:

  1. convertible_to<range_reference_t<R>, range_value_t<C>> is false.

  2. range_reference_t<R> satisfies input_range.

  3. range_reference_t<R> can be converted to range_value_t<C> through to<range_value_t<C>>. (If it couldn't, an error would be produced immediately.)

So to<C> is called recursively, constructing C with the transformed range (whose range_reference_t<R> is the same as range_value_t<C>). For the construction from the transformed range:

  1. range_reference_t<R> and range_value_t<C> are both ImmovableRange.

  2. convertible_to<range_reference_t<R>, range_value_t<C>> (i.e. convertible_to<ImmovableRange, ImmovableRange>) is false.

  3. range_reference_t<R> (i.e. ImmovableRange) satisfies input_range.

  4. range_reference_t<R> can be converted to range_value_t<C> through to<range_value_t<C>>.

So to<C> is called recursively again, transforming the range for the second time. This time, the transformation does not change any of the above four facts. As a result, to<C> is called yet again, leading to an infinite recursion.

I believe this can be fixed by stop calling to<C> recursively when range_reference_t<R> is the same as range_value_t<C>.

History
Date User Action Args
2024-03-11 21:55:38adminsetmessages: + msg13982
2023-11-18 10:32:08adminsetmessages: + msg13857
2023-11-08 00:00:00admincreate