transform_view
's sentinel<false>
not comparable with iterator<true>
Section: 25.7.9.4 [range.transform.sentinel], 25.7.14.4 [range.join.sentinel] Status: C++23 Submitter: Jonathan Wakely Opened: 2020-05-26 Last modified: 2023-11-22
Priority: 1
View all other issues in [range.transform.sentinel].
View all issues with C++23 status.
Discussion:
A user reported that this doesn't compile:
#include <list> #include <ranges> std::list v{1, 2}; // works if std::vector auto view1 = v | std::views::take(2); auto view2 = view1 | std::views::transform([] (int i) { return i; }); bool b = std::ranges::cbegin(view2) == std::ranges::end(view2);
The comparison is supposed to use operator==(iterator<Const>, sentinel<Const>)
after converting sentinel<false>
to sentinel<true>
. However, the
operator==
is a hidden friend so is not a candidate when comparing iterator<true>
with sentinel<false>
. The required conversion would only happen if we'd found the operator,
but we can't find the operator until after the conversion happens.
join_view
sentinel has a similar problem.
The proposed wording shown below has been suggested by Casey and has been implemented and tested in GCC's libstdc++.
[2020-07-17; Priority set to 1 in telecon]
Should be considered together with 3406 and 3449.
Previous resolution [SUPERSEDED]:
This wording is relative to N4861.
Modify 25.2 [ranges.syn], header
<ranges>
synopsis, as indicated:[Drafting note: The project editor is kindly asked to consider replacing editorially all of the
"using Base = conditional_t<Const, const V, V>;
" occurrences by "using Base = maybe-const<Const, V>;
" ][…] namespace std::ranges { […] namespace views { inline constexpr unspecified filter = unspecified; } template<bool Const, class T> using maybe-const = conditional_t<Const, const T, T>; // exposition-only // 25.7.9 [range.transform], transform view template<input_range V, copy_constructible F> requires view<V> && is_object_v<F> && regular_invocable<F&, range_reference_t<V>> class transform_view; […] }Modify 25.7.9.4 [range.transform.sentinel], class template
transform_view::sentinel
synopsis, as indicated:[…]namespace std::ranges { template<input_range V, copy_constructible F> requires view<V> && is_object_v<F> && regular_invocable<F&, range_reference_t<V>> && can-reference<invoke_result_t<F&, range_reference_t<V>>> template<bool Const> class transform_view<V, F>::sentinel { […] constexpr sentinel_t<Base> base() const; template<bool OtherConst> requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y); template<bool OtherConst> requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr range_difference_t<Base> operator-(const iterator<OtherConst>& x, const sentinel& y)requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>; template<bool OtherConst> requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr range_difference_t<Base> operator-(const sentinel& y, const iterator<OtherConst>& x)requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>; }; }template<bool OtherConst> requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);-4- Effects: Equivalent to:
return x.current_ == y.end_;
template<bool OtherConst> requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr range_difference_t<Base> operator-(const iterator<OtherConst>& x, const sentinel& y)requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;-5- Effects: Equivalent to:
return x.current_ - y.end_;
template<bool OtherConst> requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr range_difference_t<Base> operator-(const sentinel& y, const iterator<OtherConst>& x)requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;-6- Effects: Equivalent to:
return y.end_ - x.current_;
Modify 25.7.14.4 [range.join.sentinel], class template
join_view::sentinel
synopsis, as indicated:[…]namespace std::ranges { template<input_range V> requires view<V> && input_range<range_reference_t<V>> && (is_reference_v<range_reference_t<V>> || view<range_value_t<V>>) template<bool Const> class join_view<V>::sentinel { […] template<bool OtherConst> requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y); }; }template<bool OtherConst> requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);-3- Effects: Equivalent to:
return x.outer_ == y.end_;
[2020-08-21 Tim updates PR per telecon discussion]
As noted in the PR of LWG 3406, the return type of operator-
should be based on the constness of the iterator rather than that of the sentinel, as
sized_sentinel_for<S, I>
(24.3.4.8 [iterator.concept.sizedsentinel])
requires decltype(i - s)
to be iter_difference_t<I>
.
[2020-10-02; Status to Tentatively Ready after five positive votes on the reflector]
[2020-11-09 Approved In November virtual meeting. Status changed: Tentatively Ready → WP.]
Proposed resolution:
This wording is relative to N4861.
Modify 25.2 [ranges.syn], header <ranges>
synopsis, as indicated:
[Drafting note: The project editor is kindly asked to consider replacing editorially all of the
"using Base = conditional_t<Const, const V, V>;
" occurrences by "using Base = maybe-const<Const, V>;
" ]
[…] namespace std::ranges { […] namespace views { inline constexpr unspecified filter = unspecified; } template<bool Const, class T> using maybe-const = conditional_t<Const, const T, T>; // exposition-only // 25.7.9 [range.transform], transform view template<input_range V, copy_constructible F> requires view<V> && is_object_v<F> && regular_invocable<F&, range_reference_t<V>> class transform_view; […] }
Modify 25.7.9.4 [range.transform.sentinel], class template transform_view::sentinel
synopsis, as indicated:
[…]namespace std::ranges { template<input_range V, copy_constructible F> requires view<V> && is_object_v<F> && regular_invocable<F&, range_reference_t<V>> && can-reference<invoke_result_t<F&, range_reference_t<V>>> template<bool Const> class transform_view<V, F>::sentinel { […] constexpr sentinel_t<Base> base() const; template<bool OtherConst> requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y); template<bool OtherConst> requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr range_difference_t<Basemaybe-const<OtherConst, V>> operator-(const iterator<OtherConst>& x, const sentinel& y)requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>; template<bool OtherConst> requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr range_difference_t<Basemaybe-const<OtherConst, V>> operator-(const sentinel& y, const iterator<OtherConst>& x)requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>; }; }template<bool OtherConst> requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);-4- Effects: Equivalent to:
return x.current_ == y.end_;
template<bool OtherConst> requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr range_difference_t<Basemaybe-const<OtherConst, V>> operator-(const iterator<OtherConst>& x, const sentinel& y)requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;-5- Effects: Equivalent to:
return x.current_ - y.end_;
template<bool OtherConst> requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr range_difference_t<Basemaybe-const<OtherConst, V>> operator-(const sentinel& y, const iterator<OtherConst>& x)requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;-6- Effects: Equivalent to:
return y.end_ - x.current_;
Modify 25.7.14.4 [range.join.sentinel], class template join_view::sentinel
synopsis, as indicated:
[…]namespace std::ranges { template<input_range V> requires view<V> && input_range<range_reference_t<V>> && (is_reference_v<range_reference_t<V>> || view<range_value_t<V>>) template<bool Const> class join_view<V>::sentinel { […] template<bool OtherConst> requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y); }; }template<bool OtherConst> requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);-3- Effects: Equivalent to:
return x.outer_ == y.end_;