简体   繁体   中英

The problem of the definition of transform_view​::iterator's iterator_category?

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?

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM