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
I would agree with this behavior. They are two distinct types and I would expect that they are treated as such...
Sorry, I'm not sure what you're saying, precisely. I agree they currently _are_ different (and should be treated as such by the compiler).

The question is do you think they _should_ be distinct types. I'm just asking if it would be better if the c++ library spec _required_ that they be the exact same type.

Basically, given the downsides I listed, is there any _benefit_ to having two distinct but functionally-identical iterator types for a range.

Given that they are functionally identical, the only bugs I can imagine coming from a change like this are when somebody currently has distinct overloads or specializations for each type of iterator (which would suddenly become ambiguous). But I can't imagine many people doing that, and the spec doesn't currently guarantee that they're not the same anyway.
Yes it should be distinct types. It is part of the strong typing paradigm of the language.The signature of functions depends on the type. It is not acceptable that different types leads to the same signature => possible linker errors.

Why would it be necessary to accept multiple flavors of iterators? I would suggest to let the user of you interface provide only one. This would avoid confusion.
Xaxazak wrote:
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.


Hi,

Just my 2 cents worth, because I am not sure if you know this very well already ....

I am wondering if the error came from trying to assign one of them to the other? If so, the use of auto will take that away:

auto MyIterator = /* thing that returns an iterator */

This stops a lot of annoying hard to find errors.

Anyway, I hope all is well :+)
Registered users can post here. Sign in or register to post.