ranges::to
may cause infinite recursion if range_value_t<C>
is a non-move-constructible rangeSection: 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:
(2.1) — If
C
does not satisfyinput_range
orconvertible_to<range_reference_t<R>, range_value_t<C>>
istrue
:
[…]
(2.2) — Otherwise, if
input_range<range_reference_t<R>>
istrue
:to<C>(r | views::transform([](auto&& elem) { return to<range_value_t<C>>(std::forward<decltype(elem)>(elem)); }), std::forward<Args>(args)...);[…]
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>
).
#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:
convertible_to<range_reference_t<R>, range_value_t<C>>
is false
.
range_reference_t<R>
satisfies input_range
.
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:
range_reference_t<R>
and range_value_t<C>
are both ImmovableRange
.
convertible_to<range_reference_t<R>, range_value_t<C>>
(i.e.
convertible_to<ImmovableRange, ImmovableRange>
) is false
.
range_reference_t<R>
(i.e. ImmovableRange
) satisfies input_range
.
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.
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.
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 ofr
in the following manner:
(2.1) — If
C
does not satisfyinput_range
orconvertible_to<range_reference_t<R>, range_value_t<C>>
istrue
:
[…]
(2.2) — Otherwise, if
same_as<range_reference_t<R>, range_value_t<C>>
isfalse
andinput_range<range_reference_t<R>>
istrue
:to<C>(r | views::transform([](auto&& elem) { return to<range_value_t<C>>(std::forward<decltype(elem)>(elem)); }), std::forward<Args>(args)...);(2.3) — Otherwise, the program is ill-formed.