4008. §[range.utility.conv.to] ranges::to may cause infinite recursion if range_value_t<C> is a non-move-constructible range

Section: 25.5.7.2 [range.utility.conv.to] Status: New Submitter: S. B. Tam Opened: 2023-11-08 Last modified: 2024-03-11

Priority: 3

View other active issues in [range.utility.conv.to].

View all other issues in [range.utility.conv.to].

View all issues with New status.

Discussion:

25.5.7.2 [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>.

[2024-03-11; Reflector poll]

Set priority to 3 after reflector poll.

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

Proposed resolution:

This wording is relative to N4964.

  1. Modify 25.5.7.2 [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.