C++20 brings a more powerful iterator system, one of them is to introduce iterator_concept
on the basis of iterator_category
.
I found that the iterator_concept
and iterator_category
of many iterators in C++20 are inconsistent. Take the most famous iota_view
as an example:
using R = decltype(views::iota(0));
static_assert(random_access_range<R>);
using I = ranges::iterator_t<R>;
static_assert(same_as<typename I::iterator_category, input_iterator_tag>);
static_assert(same_as<typename I::iterator_concept, random_access_iterator_tag>);
Although R
models random_access_range
, the iterator_category
of its iterator is just an input_iterator_tag
, which is inconsistent with the iterator_concept
.
Why does C++20 introduce iterator_concept
? What is its purpose? If I implement my own iterator, how do I define iterator_concept
and iterator_category
correctly? Does iterator_category
still have a meaning in C++20?
There are differences between the C++17 (C++98) iterator model and the C++20 Ranges iterator model that are not backwards compatible. The two big ones are:
reference
that is eithervalue_type&
orvalue_type const&
.contiguous
iterators. The strongest category wasrandom_access
.The consequence of (1) is pretty significant - it means that if you have an iterator that returns a prvalue (whether proxy reference or not), it can never be stronger than an input iterator. So,
views::iota(1, 10)
, despite easily being able to support random access, is only, at best, a C++98 input range.However, you can't just... remove this requirement. Existing code that assumes C++98 iterators and uses
iterator_category
to make judgements is perfectly within its rights to assume that ifiterator_category
is, say,bidirectional_iterator_tag
, that itsreference
is some kind of lvalue reference tovalue_type
.What
iterator_concept
does is add a new C++20 layer that allows an iterator to both advertise its C++98/17 category and, distinctly, advertise its C++20 category. So going back to theiota_view<int, int>
example, that view's iterator hasiterator_category
set toinput_iterator_tag
(because thereference
is a prvalue and so it does not satisfy the old requirements for even forward) but itsiterator_concept
is set torandom_access_iterator_tag
(because once we drop that restriction, we can easily support all the random access restrictions).In [iterator.concepts.general], we have this magic function
ITER_CONCEPT(I)
which helps us figure out what tag to use in C++20.The issue with (2) is that it was hard to just add a new
contiguous_iterator_tag
before due to the way that various C++98/17 code would check for that tag (lots of code might check for exactlyrandom_access_iterator_tag
). Theiterator_concept
approach avoids this problem by also introducing concepts that directly check the right thing for you (i.e. therandom_access_iterator
concept checks thatITER_CONCEPT(I)
derives fromrandom_access_iterator_tag
, not that it simply is that).Guidelines:
std::iterator_traits<I>::iterator_category
.std::meow_iterator
conceptsiterator_category
alias and make sure you follow the forward iterator/reference restriction (or... don't, but it's on you)iterator_category
anditerator_concept
type aliases.