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.

4226. to_input_view::iterator cannot be compared to its const sentinel

Section: 25.7.35 [range.to.input] Status: New Submitter: Hewill Kang Opened: 2025-03-15 Last modified: 2025-03-22

Priority: Not Prioritized

View all issues with New status.

Discussion:

to_input_view was recently added to the working draft by P3137R3.

Consider: (demo):

#include <ranges>

int main() {
  auto r = std::views::single(0)
         | std::views::chunk(1)
         | std::views::to_input;

  r.begin() == std::as_const(r).end(); // #1, error
  r.begin() ==               r.cend(); // #2, error
}

In #1, r.begin() returns to_input_view<chunk_view>::iterator<false>, while the latter returns chunk_view::iterator<true>. Since the former can only be compared with chunk_view::iterator<false> that cannot be converted from chunk_view::iterator<true>, the two are incomparable.

This can be fixed by adding the following overload to to_input_view::iterator:

template<bool OtherConst = !Const>
  requires sentinel_for<sentinel_t<maybe-const<OtherConst, V>>, iterator_t<Base>>
friend constexpr bool operator==(const iterator& x, const sentinel_t<maybe-const<OtherConst, V>>& y)
{ return x.current_ == y; }

Unfortunately, it still doesn't resolve #2, because r.cend() returns basic_const_iterator<chunk_view::iterator<true>>, which cannot be compared to any non-copyable iterators as its operator==(const S& s) requires S to be a sentinel type, which rules out to_input_view::iterator, so the constraint is not satisfied.

I believe we may need to introduce a custom sentinel class for to_input_view.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 25.7.35.2 [range.to.input.view] as indicated:

    namespace std::ranges {
      template<input_range V>
        requires view<V>
      class to_input_view : public view_interface<to_input_view<V>> {
        V base_ = V();                          // exposition only
    
        template<bool Const>
        class iterator;                         // exposition only
        template<bool Const>
        class sentinel;                         // exposition only
        […]
      };
      […]
    }
    
    […]
    constexpr auto end() requires (!simple-view<V>);
    

    -?- Effects: Equivalent to:

    return sentinel<false>(ranges::end(base_));
    
    constexpr auto end() const requires range<const V>;
    

    -4- Effects: Equivalent to:

    return sentinel<true>(ranges::end(base_));
    
  2. Modify 25.7.35.3 [range.to.input.iterator] as indicated:

    namespace std::ranges {
      template<input_range V>
        requires view<V>
      template<bool Const>
      class to_input_view<V>::iterator {
        […]
        friend constexpr bool operator==(const iterator& x, const sentinel_t<Base>& y);
    
        friend constexpr difference_type operator-(const sentinel_t<Base>& y, const iterator& x)
          requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
        friend constexpr difference_type operator-(const iterator& x, const sentinel_t<Base>& y)
          requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
        […]
      };
    }
    
    […]
    
    friend constexpr bool operator==(const iterator& x, const sentinel_t<Base>& y);
    

    -7- Returns: x.current_ == y.

    
    friend constexpr difference_type operator-(const sentinel_t<Base>& y, const iterator& x)
      requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
    

    -8- Returns: y - x.current_.

    
    friend constexpr difference_type operator-(const iterator& x, const sentinel_t<Base>& y)
      requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
    

    -9- Returns: x.current_ - y.

  3. Add [range.to.input.sentinel] after [range.to.input.iterator] as indicated:

    
    namespace std::ranges {
      template<input_range V>
        requires view<V>
      template<bool Const>
      class to_input_view<V>::sentinel {
        using Base = maybe-const<Const, V>;                         // exposition only
        sentinel_t<Base> end_ = sentinel_t<Base>();                 // exposition only
        constexpr explicit sentinel(sentinel_t<Base> end);          // exposition only
    
      public:
        sentinel() = default;
        constexpr sentinel(sentinel<!Const> other)
          requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
    
        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<maybe-const<OtherConst, V>>
          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<maybe-const<OtherConst, V>>
          operator-(const sentinel& x, const iterator<OtherConst>& y);
      };
    }
    
    
    constexpr explicit sentinel(sentinel_t<Base> end);
    

    -?- Effects: Initializes end_ with std::move(end).

    
    constexpr sentinel(sentinel<!Const> other)
      requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
    

    -?- Effects: Initializes end_ with std::move(other.end_).

    
    constexpr sentinel_t<Base> base() const;
    

    -?- Effects: Equivalent to: return end_;

    
    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);
    

    -?- 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<maybe-const<OtherConst, V>>
      operator-(const iterator<OtherConst>& x, const sentinel& y);
    

    -?- 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<maybe-const<OtherConst, V>>
      operator-(const sentinel& x, const iterator<OtherConst>& y);
    

    -?- Effects: Equivalent to: return x.end_ - y.current_;