3737. take_view::sentinel should provide operator-

Section: 25.7.10.3 [range.take.sentinel] Status: C++23 Submitter: Hewill Kang Opened: 2022-07-15 Last modified: 2023-11-22

Priority: 3

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

View all issues with C++23 status.

Discussion:

This issue is part of NB comment US 47-109 26 [ranges] Resolve open issues

When the underlying range is not a sized_range, the begin and end functions of take_view return counted_iterator and take_view::sentinel respectively. However, the sentinel type of the underlying range may still model sized_sentinel_for for its iterator type, and since take_view::sentinel can only be compared to counted_iterator, this makes take_view no longer able to compute the distance between its iterator and sentinel.

We are needlessly losing functionality here. Since calculating the distance, in this case, is still simple, i.e. we just need to compute the minimum of counted_iterator::count and the difference between the underlying iterator and sentinel, I think providing operator- for take_view::sentinel does bring some value.

[2022-08-23; Reflector poll]

Set priority to 3 after reflector poll.

Some P0 votes, but with objections: "This seems like a) a feature not a bug - of fairly limited utility?, and b) I’d like to see an implementation (maybe it’s in MSVC?) to be sure there isn’t a negative interaction we’re not thinking of."

Previous resolution [SUPERSEDED]:

This wording is relative to N4910.

  1. Modify 25.7.10.3 [range.take.sentinel], class template take_view::sentinel synopsis, as indicated:

    namespace std::ranges {
      template<view V>
      template<bool Const>
      class take_view<V>::sentinel {
      private:
        using Base = maybe-const<Const, V>;                                     // exposition only
        template<bool OtherConst>
          using CI = counted_iterator<iterator_t<maybe-const<OtherConst, V>>>;  // exposition only
        sentinel_t<Base> end_ = sentinel_t<Base>();                             // exposition only
      public:
        […]
        friend constexpr bool operator==(const CI<Const>& y, const sentinel& x);
    
        template<bool OtherConst = !Const>
          requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
        friend constexpr bool operator==(const CI<OtherConst>& y, const sentinel& x);
        
        friend constexpr range_difference_t<Base>
          operator-(const sentinel& x, const CI<Const>& y)
            requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
    
        template<bool OtherConst = !Const>
          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 CI<OtherConst>& y);
    
        friend constexpr range_difference_t<Base>
          operator-(const CI<Const>& x, const sentinel& y)
            requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
    
        template<bool OtherConst = !Const>
          requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
        friend constexpr range_difference_t<maybe-const<OtherConst, V>>
          operator-(const CI<OtherConst>& x, const sentinel& y);
      };
    }
    
    […]
    friend constexpr bool operator==(const CI<Const>& y, const sentinel& x);
    
    template<bool OtherConst = !Const>
      requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
    friend constexpr bool operator==(const CI<OtherConst>& y, const sentinel& x);
    

    -4- Effects: Equivalent to: return y.count() == 0 || y.base() == x.end_;

    
    friend constexpr range_difference_t<Base> 
      operator-(const sentinel& x, const CI<Const>& y)
        requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>; 
    
    template<bool OtherConst = !Const>
      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 CI<OtherConst>& y);
    

    -?- Effects: Equivalent to: return ranges::min(y.count(), x.end_ - y.base());

    
    friend constexpr range_difference_t<Base>
      operator-(const CI<Const>& x, const sentinel& y)
        requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
    
    template<bool OtherConst = !Const>
      requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
    friend constexpr range_difference_t<maybe-const<OtherConst, V>>
      operator-(const CI<OtherConst>& x, const sentinel& y);
    
    

    -?- Effects: Equivalent to: return -(y - x);

[Kona 2022-11-08; Discussed at joint LWG/SG9 session. Move to Open]

[2022-11-09 Tim updates wording following LWG discussion]

This case is only possible if the source view is not a sized_range, yet its iterator/sentinel types model sized_sentinel_for (typically when source is an input range). In such a case we should just have begin compute the correct size so that end can just return default_sentinel.

[Kona 2022-11-10; Move to Immediate]

[2022-11-12 Approved at November 2022 meeting in Kona. Status changed: Immediate → WP.]

Proposed resolution:

This wording is relative to N4917.

  1. Modify 25.7.10.2 [range.take.view], class template take_view synopsis, as indicated:

    namespace std::ranges {
      template<view V>
      class take_view : public view_interface<take_view<V>> {
      private:
        […]
      public:
        […]
        constexpr auto begin() requires (!simple-view<V>) {
          if constexpr (sized_range<V>) {
            if constexpr (random_access_range<V>) {
              return ranges::begin(base_);
            } else {
              auto sz = range_difference_t<V>(size());
              return counted_iterator(ranges::begin(base_), sz);
            }
          } else if constexpr (sized_sentinel_for<sentinel_t<V>, iterator_t<V>>) {
            auto it = ranges::begin(base_);
            auto sz = std::min(count_, ranges::end(base_) - it);
            return counted_iterator(std::move(it), sz);
          } else {
            return counted_iterator(ranges::begin(base_), count_);
          }
        }
    
        constexpr auto begin() const requires range<const V> {
          if constexpr (sized_range<const V>) {
            if constexpr (random_access_range<const V>) {
              return ranges::begin(base_);
            } else {
              auto sz = range_difference_t<const V>(size());
              return counted_iterator(ranges::begin(base_), sz);
            }
          } else if constexpr (sized_sentinel_for<sentinel_t<const V>, iterator_t<const V>>) {
            auto it = ranges::begin(base_);
            auto sz = std::min(count_, ranges::end(base_) - it);
            return counted_iterator(std::move(it), sz);
          } else {
            return counted_iterator(ranges::begin(base_), count_);
          }
        }
    
        constexpr auto end() requires (!simple-view<V>) {
          if constexpr (sized_range<V>) {
            if constexpr (random_access_range<V>)
              return ranges::begin(base_) + range_difference_t<V>(size());
            else
              return default_sentinel;
          } else if constexpr (sized_sentinel_for<sentinel_t<V>, iterator_t<V>>) {
            return default_sentinel;
          } else {
            return sentinel<false>{ranges::end(base_)};
          }
        }
    
        constexpr auto end() const requires range<const V> {
          if constexpr (sized_range<const V>) {
            if constexpr (random_access_range<const V>)
              return ranges::begin(base_) + range_difference_t<const V>(size());
            else
              return default_sentinel;
          } else if constexpr (sized_sentinel_for<sentinel_t<const V>, iterator_t<const V>>) {
            return default_sentinel;
          } else {
            return sentinel<true>{ranges::end(base_)};
          }
        }
        
        […]
      };
      […]
    }