Hello C++ experts,

I wonder why the standard has std::move_sentinel but not std::counted_sentinel, because in my opinion the latter is super valuable in certain situations.

Suppose we have a non-sized range:

 auto l = std::list{1, 2, 0, 3, 42, 42, 5, 6, 7, 9, 10, 42, 0, 11};
 auto r = l | std::views::filter([](int x) { return x != 42; });

And we want to apply views::split or views::chunk_by to it to split it into subranges (https://godbolt.org/z/sKh3zM1Wf):

auto split = r | std::views::split(0);
for (auto subrange : split)
  std::println("{} (size: {})", subrange, std::ranges::distance(subrange));
auto chunk = r | std::views::chunk_by(std::less{});
for (auto subrange : chunk)
  std::println("{} (size: {})", subrange, std::ranges::distance(subrange));

Unfortunately, these subranges are not sized_range even if we have iterated through them from beginning to end, since they just contain two iterators of the original range which cannot be subtracted.

In order to get their sizes, I have to recalculate their sizes using ranges::distance, which makes it necessary to iterate over them again.

However, if we introduce a std::counted_sentinel corresponding to std::counted_iterator for comparison with the former's underlying iterator, for example:

namespace std {

template<semiregular S>
struct counted_sentinel {
  counted_sentinel() = default;
  constexpr explicit counted_sentinel(S end) : end_(std::move(end)) { }
  constexpr S base() const { return end_; }
  template<class I> requires sentinel_for<S, I>
  friend constexpr bool operator==(
    const counted_iterator<I>& x, const counted_sentinel& y)
      { return x.base() == y.base(); }
private:
  S end_;
};

}

This allows us to wrap the origin range from the previous example to getĀ subranges that model sized_range (https://godbolt.org/z/arhdqMKxo):

auto l = std::list{1, 2, 0, 3, 42, 42, 5, 6, 7, 9, 10, 42, 0, 11};
auto r = l | std::views::filter([](int x) { return x != 42; });
auto s = std::ranges::subrange(std::counted_iterator(r.begin(), 0), 
                               std::counted_sentinel(r.end()));
auto split = s | std::views::split(0);
for (auto subrange : split)
  std::println("{} (size: {})", subrange, subrange.size());
auto chunk = s | std::views::chunk_by(std::less{});
for (auto subrange : chunk)
  std::println("{} (size: {})", subrange, subrange.size());

I think this is a big enhancement to avoid redundant calculations, and I think this can also be applied to algorithms that returnĀ subranges such as ranges::find_last etc. so that they always return a subrange that models a sized_range.

What do you guys think? Is this worth adding to the standard? I believe there may be more useful usage models that I haven't thought of yet.

Hewill