discussion on std::ranges::iterator_t<T> being distinct from T::iterator (and same for begin)

I recently spent an hour or so on a bug that came down to std::ranges::const_iterator_t<T> sometimes being different from T::const_iterator. For example:

===
static_assert(std::is_same_v<std::list<int>::const_iterator, std::ranges::const_iterator_t<std::list<int>>>);
===
gives (via g++)
===
note: ‘std::is_same_v<std::_List_const_iterator<int>, std::basic_const_iterator<std::_List_iterator<int> > >’ evaluates to false
===

I was wondering if this situation (multiple flavors of equivalent iterators) is reasonable or problematic.

The downsides are:
- Compiling when using multiple iterator flavors can lead to multiple equivalent template instantiations leading to binary-file bloat.
- When specializing for iterators of a range, you'll need to be aware there's possibly two (or more?) different flavors of each type of iterator for said range depending on how they were generated (T::begin() vs std::ranges::begin(T)).
- There's no guarantee you can assign one flavor from the other even if they are equivalent.


There are a couple of solutions with downsides of their own.

1. Require the non-member type to always alias the member type if it exists, eg: require std::ranges::const_iterator_t<T> to alias T::const_iterator if it exists (probably via a concept).
Using aliases like this prevents specializing for all std::ranges::const_iterator_t<T> via basic_const_iterator<T>, but AFAICT that's not guaranteed by the standard because the exact type of std::ranges::const_iterator_t<T> isn't specified.

2. require all ranges to provide all possible iterators (const or not, reverse or not, range-const or not, etc ...), which would require a lot of work and break thiry-party ranges. (by range-const I mean what iterator_t<T const> gives, which can be different from const_iterator_t<T>).

3. require all "modified/qualified" iterators of a type (eg reverse, const, and/or range-const) to be aliases of std::ranges::(const/reverse/etc)_iterator_t<T>, ensuring only one flavor exists. This prevents specialized implementations (but is there any need for them, since the "base" iterator can already provide all the optimal functionality for generating "modified/qualified" iterators).

4. leave things as they are.


IMHO #1 is best, and reasonably feasible. It does have some small potential to break some code relying on unspecified standard-library behavior, but that's always a risk.


What do you all think about this problem?


(Apologies, the post format buttons don't work on my firefox or my chromium and there's no format guide - mods feel free to edit)
Last edited on
Registered users can post here. Sign in or register to post.