2

The standard defines a variety of range adaptors in [range.adaptors], and some of them have their own iterator types.

In order to standardize the iterator_category of these iterators, the standard also specifies how they are defined. For example, in [range.transform.iterator-2], the iterator_category of transform_view​::​iterator is defined as follows:

The member typedef-name iterator_­category is defined if and only if Base models forward_­range. In that case, iterator​::​iterator_­category is defined as follows: Let C denote the type iterator_­traits<iterator_­t<Base>>​::​iterator_­category.

If is_­lvalue_­reference_­v<invoke_­result_­t<F&, range_­reference_­t<Base>>> is true, then

  • if C models derived_­from<contiguous_­iterator_­tag>, iterator_­category denotes random_­access_­iterator_­tag;

  • otherwise, iterator_­category denotes C.

Otherwise,iterator_­category denotes input_­iterator_­tag.

But this definition seems to have some problems, consider the following case:

vector v{1, 2, 3, 4, 5};
auto r = views::iota(0, 5) | 
         views::transform([&](int i) -> int& { return v[i]; });

using I = ranges::iterator_t<decltype(r)>;
static_assert(random_access_iterator<I>);
static_assert(same_as<I::iterator_concept, random_access_iterator_tag>);

static_assert(__detail::__cpp17_randacc_iterator<I>);
static_assert(same_as<I::iterator_category, input_iterator_tag>);

Since r is perfect random_access_range in C++20, its iterator models random_access_iterator, however, according to the description above, since iota_view::iterator_category is only input_iterator_tag, the iterator_category of iterator I is also only input_iterator_tag.

But obviously, iterator I meets all the requirements of LegacyRandomAccessIterator, so it should be more reasonable if its iterator_category is random_access_iterator.

Is this a standard defect? Or is there a consideration behind this?

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • "*But obviously, iterator I meets all the requirements of LegacyRandomAccessIterator,*" Does it? – Nicol Bolas Jul 17 '21 at 13:30
  • @NicolBolas. Can you tell me which requirement it does not meet? What the lambda return is an lvalue reference, I can’t understand how this is related to that question. – 康桓瑋 Jul 17 '21 at 13:35

1 Answers1

5

According to C++20, the iterator category for a transform_view is determined as follows:

iterator​::​iterator_­category is defined as follows: Let C denote the type iterator_­traits<iterator_­t<Base>>​::​iterator_category.

  • If is_­lvalue_­reference_­v<invoke_­result_­t<F&, range_reference_­t<Base>>> is true, then
    • if C models derived_­from<contiguous_­iterator_­tag>, iterator_­category denotes random_­access_­iterator_­tag;
    • otherwise, iterator_­category denotes C.
  • Otherwise, iterator_­category denotes input_­iterator_tag.

So if your functor returns an lvalue reference, then the iterator category is derived from the iterator category of the base iterator type. And the base range that provides the base iterator is views::iota.

However, because iota is a prvalue range (its iterators return prvalues, not references), its iterator_category cannot be anything other than std::input_iterator_tag. So in the above text for transform_view, C will be std::input_iterator_tag. So the category for transform_view will be std::input_iterator_tag, no matter what your functor returns.

You should really avoid caring about iterator_category when you're using std::ranges-based code. If you know you're dealing with C++20 ranges, then you should be using the iterator_concept.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I want to know if we can use crtp-like technology to automatically detect the category of the iterator based on the functions it provides (the definition of `std::xxx_iterator` and [`cpp17-xxx-iterator`](http://eel.is/c++draft/iterator.requirements#iterator.traits-2)), and then Inject the *correct* `iterator_category` and `iterator_concept` into the iterator. – 康桓瑋 Jul 18 '21 at 04:03
  • @康桓瑋: The "correct" iterator category is *already there*. A transform view of an iota view is *not* a C++17 RandomAccessIterator. It is a C++20 `std::random_access_iterator`. What you don't seem to understand is that these are *not* the same thing. – Nicol Bolas Jul 18 '21 at 04:17
  • Why do you keep saying that "*A transform_view of an iota_view is not a C++17 RandomAccessIterator*" even though it [meets all the requirements](https://godbolt.org/z/vT3MhGWq6) of [C++17 RandomAccessIterator](https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator)? – 康桓瑋 Jul 18 '21 at 04:47
  • 1
    @康桓瑋: I keep saying it because it's true. To be a C++17 RandomAccessIterator, it must be a C++17 ForwardIterator. And one of the requirements of a Forward Iterator is that `iterator::reference` *must* be `iterator::value_type&` (or `&&`). And [`views::iota` doesn't qualify, even for your own definition](https://godbolt.org/z/7drrjj4vW). Therefore, `views::transform` does not qualify, *no matter what* your functor returns. – Nicol Bolas Jul 18 '21 at 05:24
  • @康桓瑋 `iota_view::iterator` is not even a *Cpp17ForwardIterator* because it fails to meet this requirement: http://eel.is/c++draft/forward.iterators#6. The same holds for `std::vector::iterator`, even though its `iterator_category` claims to be random access. – metalfox Jul 19 '21 at 09:00
  • @metalfox. I am not talking about `iota_view::iterator` from start to finish, I am talking about `transform_view::iterator`, in my case its type is `ranges::transform_view, main():: >::_Iterator`, and [its `value_type` is `int` and `reference` type is `int&` not `int`](https://godbolt.org/z/af1jsdcxK). – 康桓瑋 Jul 19 '21 at 09:59
  • 2
    @康桓瑋 Yes, I get your point. But `static_assert(std::is_lvalue_reference_v)` passes, and that doesn’t mean it satisfies [forward.iterators]\6. It would be hard to device a relatively simple set of rules that ensures compliance with that requirement. – metalfox Jul 19 '21 at 11:03
  • @metalfox. I think the answer to this question should be that the category of an iterator (`transform_view::iterator`) built on top of another iterator (`iota_view::iterator`) cannot be higher than that of the underlying iterator even if it meets all the requirements of a higher category. – 康桓瑋 Jul 19 '21 at 11:14
  • @康桓瑋: Isn't that what I said? I showed the determination of `transform_view`'s iterator category in the first quote, then explained what `iota_view`'s category was, and then said "So the category for transform_view will be std::input_iterator_tag, no matter what your functor returns." – Nicol Bolas Jul 19 '21 at 13:18
  • @NicolBolas I have already indicated in the question why the `iterator_category` of `transform_view::iterator` in this case is `input_iterator_tag`, I just want to know whether it is `LegacyRandomAccessIterator` in some sense? But as you said, it will never be higher than `LegacyInputIterator`. – 康桓瑋 Jul 19 '21 at 13:35
  • 1
    @康桓瑋: "*I just want to know whether it is LegacyRandomAccessIterator in some sense?*" Not in any way that is legitimately useful. – Nicol Bolas Jul 19 '21 at 14:22