elements_view
insufficiently constrainedSection: 25.7.23.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.
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.
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 25.7.23.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 25.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.