Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the rationale behind the iterator/sentinel of range adaptors providing the base() accessor?

Legacy iterator adaptors such as reverse_iterator and move_iterator, or C++20/23 newly introduced adaptors such as counted_iterator, basic_const_iterator, and move_sentinel, all of them provide base() member to allow us to access the underlying iterator/sentinel:

constexpr const I& base() const& noexcept { return current_­; }
constexpr I        base() &&              { return std​::​move(current_­); }

However, for a series of iterators/sentinels of range adaptors in <ranges>, I found that they do not all provide a base(). For example, split_view's "outer-iterator" provides a base(), while lazy_split_view does not (godbolt):

string_view s = "one two three four";

// ["one", "two", "three", "four"]
auto r1 = s | views::split(' ');
for (auto it = r1.begin(); it != r1.end(); ++it)
  cout << *it.base() << " ";      // prints 'o' 't' 't' 'f'

// ["one", "two", "three", "four"]
auto r2 = s | views::lazy_split(' ');
for (auto it = r2.begin(); it != r2.end(); ++it)
  cout << *it.base() << " ";      // error, no 'base' member

As another example, chunk_view::iterator provides a base(), but slide_view::iterator, which also belongs to windowing range adaptors, does not (godbolt):

array v = {1, 2, 3, 4, 5};
// [[1, 2], [3, 4], [5]]
auto r1 = v | views::chunk(2);
for (auto it = r1.begin(); it != r1.end(); ++it)
  cout << *it.base() << " ";      // prints 1 3 5 

// [[1, 2], [2, 3], [3, 4], [4, 5]]
auto r2 = v | views::slide(2);
for (auto it = r2.begin(); it != r2.end(); ++it)
  cout << *it.base() << " ";      // error, no 'base' member

And for sentinel adaptors, there also seem to be inconsistencies: both filter_view/transform_view::sentinel provide base(), but join_view/split_view::sentienl with the same layout do not.

So, what is the rationale for these iterator/sentinel adaptors that provide the base()? I could not find any considerations behind this from historical documents, so I'm wondering what design philosophy the standard is based on.

like image 763
康桓瑋 Avatar asked Oct 21 '25 07:10

康桓瑋


1 Answers

In some cases, I deliberately did not provide base() when doing so could leak implementation details (zip and adjacent - we don't want to mandate any particular form of iterator storage) and/or be confusing (chunk_view::outer-iterator - base() will change while you iterate over the chunk, because that's the only way to implement this for input iterators).

lazy_split's outer-iterator - at least for input ranges - would have the same problem as chunk.

I probably didn't provide base() for slide because I didn't provide it for adjacent, but it should be fine to add it.

There's nothing meaningful you can do with a join_view's sentinel's wrapped sentinel (the iterator doesn't provide base() - and for good reasons), so there's no motivation to provide access to it. It's probably reasonable to provide base() for split's sentinel though, since we provided one for its iterator.

like image 132
T.C. Avatar answered Oct 23 '25 22:10

T.C.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!