This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
Section: 26.7.22.2 [range.elements.view] Status: New Submitter: Hui Xie Opened: 2022-10-21 Last modified: 2022-11-01
Priority: 2
View all other issues in [range.elements.view].
View all issues with New status.
Discussion:
This issue came up when I tried to integrate the C++23 changes to tuple-like into ranges::elements_view in libc++. Given the following test:
Using SubRange = ranges::subrange<MoveOnlyIter, Sent>; std::vector<SubRange> srs = ...; // a vector of subranges for(auto&& iter : srs | views::elements<0>){ }
The above code results in a hard error in deciding the iterator_category (The base is a random access range so it should exist). The immediate hard error complains that the following expression is invalid.
std::get<N>(*current_);
Note that even if iterator_category does not complain, it will complain later when we dereference the iterator.
Here are the declarations of the "get" overloads for subrange:template<size_t N, class I, class S, subrange_kind K> requires ((N == 0 && copyable<I>) || N == 1) constexpr auto get(const subrange<I, S, K>& r); template<size_t N, class I, class S, subrange_kind K> requires (N < 2) constexpr auto get(subrange<I, S, K>&& r);
Note that the first overload requires copyable<I> which is false and the second overload requires an rvalue, which is also not the case. So we don't have a valid "get" in this case.
But why does elements_view allow the instantiation in the first place? Let's look at its requirements:template<class T, size_t N> concept returnable-element = // exposition only is_reference_v<T> || move_constructible<tuple_element_t<N, T>>; template<input_range V, size_t N> requires view<V> && has-tuple-element<range_value_t<V>, N> && has-tuple-element<remove_reference_t<range_reference_t<V>>, N> && returnable-element<range_reference_t<V>, N> class elements_view;
It passed the "is_reference_v<range_reference_t<V>>" requirement, because it is "subrange&". Here the logic has an assumption: if the tuple-like is a reference, then we can always "get" and return a reference. This is not the case for subrange. subrange's get always return by value.
[2022-11-01; Reflector poll]
Set priority to 2 after reflector poll.
"The actual issue is that P2165 broke has-tuple-element for this case. We should unbreak it."
Proposed resolution:
This wording is relative to N4917.
[Drafting Note: Three mutually exclusive options are prepared, depicted below by Option A, Option B, and Option C, respectively.]
Option A: Properly disallow this case (preferred solution)
Modify 26.7.22.2 [range.elements.view] as indicated:
namespace std::ranges { […] template<class T, size_t N> concept returnable-element = // exposition only requires { std::get<N>(declval<T>()); } && is_reference_v<T> || move_constructible<tuple_element_t<N, T>>; […] }
Option B: Relax subrange's get to have more overloads. Since subrange's non-const begin unconditionally moves the iterator (even for lvalue-reference),
[[nodiscard]] constexpr I begin() requires (!copyable<I>); Effects: Equivalent to: return std::move(begin_);
if we add more get overloads, it would work. The non-const lvalue-ref overload would work (and it also moves because non-const lvalue begin moves). This solution would make another way to let subrange's iterator in moved-from state, which is not good.
Modify 26.2 [ranges.syn] as indicated:
[…] namespace std::ranges { […] template<size_t N, class I, class S, subrange_kind K> requires ((N == 0 && copyable<I>) || N == 1) constexpr auto get(const subrange<I, S, K>& r); template<size_t N, class I, class S, subrange_kind K> requires (N < 2) constexpr auto get(subrange<I, S, K>&& r); template<size_t N, class I, class S, subrange_kind K> requires ((N == 0 && constructible_from<I, const I&&>) || N == 1) constexpr auto get(const subrange<I, S, K>&& r); template<size_t N, class I, class S, subrange_kind K> requires (N < 2) constexpr auto get(subrange<I, S, K>& r); } […]
Option C: Make subrange's get to return by reference. This seems to significantly change the subrange's tuple protocol, which is not ideal.