13

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?

康桓瑋
  • 33,481
  • 5
  • 40
  • 90

1 Answers1

24

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:

  1. The C++98 model requires that forward iterators have a reference that is either value_type& or value_type const&.
  2. The C++98 model does not allow for contiguous iterators. The strongest category was random_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 if iterator_category is, say, bidirectional_iterator_tag, that its reference is some kind of lvalue reference to value_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 the iota_view<int, int> example, that view's iterator has iterator_category set to input_iterator_tag (because the reference is a prvalue and so it does not satisfy the old requirements for even forward) but its iterator_concept is set to random_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 exactly random_access_iterator_tag). The iterator_concept approach avoids this problem by also introducing concepts that directly check the right thing for you (i.e. the random_access_iterator concept checks that ITER_CONCEPT(I) derives from random_access_iterator_tag, not that it simply is that).


Guidelines:

  • If you're using an iterator in C++17, use std::iterator_traits<I>::iterator_category.
  • If you're using an iterator in C++20, use the std::meow_iterator concepts
  • If you're writing an iterator in C++17, add the iterator_category alias and make sure you follow the forward iterator/reference restriction (or... don't, but it's on you)
  • If you're writing an iterator in C++20, follow the guidance in P2259 which has a good description of the problem and how and when to provide the iterator_category and iterator_concept type aliases.
T.C.
  • 133,968
  • 17
  • 288
  • 421
Barry
  • 286,269
  • 29
  • 621
  • 977
  • +100. Thanks, Barry. In addition, `iterator_concept` and `iterator_category` seem to be inconsistent with whether the iterator is `const`. For example, in [range.transform#iterator-1.1](http://eel.is/c++draft/range.transform#iterator-1.1), the `iterator_concept` of `transform_view`'s iterator is defined according to the original `V`, but `iterator_category` is based on `Base` (maybe `const V` or `V`). Would you like to explain a little bit about this inconsistency? – 康桓瑋 May 19 '21 at 16:14
  • 1
    @康桓瑋 It's inconsistent yeah, but I don't think it makes a huge difference (I don't know that the library cares about the case where like... `V` is random access but `V const` is forward. Like... why?) More typically, `V` is random access but `V const` isn't even a range, in which case the distinction wouldn't matter as much. – Barry May 19 '21 at 16:22
  • I recall some musings about making `V const` input if you need to cache in `begin` but can't because of `const`. – T.C. May 19 '21 at 19:03
  • @T.C. But if `V` does not provide a `begin() const` function, `transform_view::iterator` will not be instantiated because of the constraints of `transform_view::begin() const`. I still can't understand the problems that `const` can cause here. – 康桓瑋 May 19 '21 at 23:02
  • 1
    @康桓瑋 Please stop using sock puppet accounts to give Barry reputation. It will go to his head. – Yakk - Adam Nevraumont May 20 '21 at 13:10
  • @Yakk-AdamNevraumont wat – Barry May 20 '21 at 21:23
  • @barry they upvoted you 100 times! "+100": I mean, show some restraint. – Yakk - Adam Nevraumont May 20 '21 at 22:18
  • @Yakk-AdamNevraumont lol. No restraint necessary :-) – Barry May 20 '21 at 22:20
  • 1
    This is [LWG3555](https://cplusplus.github.io/LWG/lwg-active.html#3555). – 康桓瑋 May 27 '21 at 15:40