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.
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.