This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

### 3448. `transform_view`'s `sentinel<false>` not comparable with `iterator<true>`

Section: 24.7.7.4 [range.transform.sentinel], 24.7.12.4 [range.join.sentinel] Status: WP Submitter: Jonathan Wakely Opened: 2020-05-26 Last modified: 2020-11-09

Priority: 1

View all other issues in [range.transform.sentinel].

View all issues with WP 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.

As Patrick noted, the `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.

> 1. Modify 24.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

>    >       // 24.7.7 [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;
>    >     […]
>    >     }

> 2. Modify 24.7.7.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_;`

> 3. Modify 24.7.12.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>` (23.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.

1. Modify 24.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

   >       // 24.7.7 [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;
   >     […]
   >     }

2. Modify 24.7.7.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_;`

3. Modify 24.7.12.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_;`