This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++23 status.
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(i) and 3449(i).
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::sentinelsynopsis, 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::sentinelsynopsis, 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(i), 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_;