Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
josuttis_nm_c20_the_complete_guide.pdf
Скачиваний:
44
Добавлен:
27.03.2023
Размер:
5.85 Mб
Скачать

178

Chapter 7: Utilities for Ranges and Views

std::vector vec{1, 2, 3, 4};

 

auto pos1 = vec.begin();

 

decltype(pos1)::iterator_category

// type std::random_access_iterator_tag

decltype(pos1)::iterator_concept

// type std::contiguous_iterator_tag

auto v = std::views::iota(1); auto pos2 = v.begin();

decltype(pos2)::iterator_category // type std::input_iterator_tag decltype(pos2)::iterator_concept // type std::random_access_iterator_tag

Note that std::iterator_traits do not provide a member iterator_concept and that for iterators, a member iterator_category may not always be defined.

The iterator concepts and the range concepts take the new C++20 categorization into account. For code that has to deal with iterator categories, this has the following consequences since C++20:3

Use iterator concepts and range concepts instead of std::iterator_traits<I>::iterator_category to check the category.

Think about providing iterator_category and/or iterator_concept according to the hints in http://wg21.link/p2259 if you implement your own iterator types.

Note also that valid C++20 input iterators may not be C++17 iterators at all (e.g., by not providing copying). For these iterators, the traditional iterator traits do not work. For this reason, since C++20:4

Use std::iter_value_t

instead of iterator_traits<>::value_type.

Use std::iter_reference_t

instead of iterator_traits<>::reference.

Use std::iter_difference_t

instead of iterator_traits<>::difference_type.

7.3New Iterator and Sentinel Types

For (better) support for ranges and views, C++20 introduces a couple of new iterator and sentinel types:

std::counted_iterator for an iterator that itself has a count to specify the end of a range

std::common_iterator for a common iterator type that can be used for two iterators that have different types

std::default_sentinel_t for an end iterator that forces an iterator to check for its end

std::unreachable_sentinel_t for an end iterator that can never be reached, meaning that the range is endless

std::move_sentinel for an end iterator that maps copies to moves

3 Thanks to Barry Revzin for pointing this out in http://stackoverflow.com/questions/67606563.

4 Thanks to Hui Xie for pointing this out.

7.3 New Iterator and Sentinel Types

179

7.3.1std::counted_iterator

A counted iterator is an iterator that has a counter for the maximum number of elements to iterate over. There are two ways to use such an iterator:

• Iterating while checking how many elements are left:

for (std::counted_iterator pos{coll.begin(), 5}; pos.count() > 0; ++pos) { std::cout << *pos << '\n';

}

std::cout << '\n';

• Iterating while comparing with a default sentinel:

for (std::counted_iterator pos{coll.begin(), 5}; pos != std::default_sentinel; ++pos) {

std::cout << *pos << '\n';

}

This feature is used when the view adaptor std::ranges::counted() yields a subrange for iterators that are not random-access iterators.

Table Operations of class std::counted_iterator<> lists the API of a counted iterator.

Operation

Effect

countedItor pos{}

Creates a counted iterator that refers to no element (count is 0)

countedItor pos{pos2, num}

Creates a counted iterator of num elements starting with pos2

countedItor pos{pos2}

Creates a counted iterator as a (type converted) copy of a counted

 

iterator pos2

pos.count()

Yields how many elements are left (0 means we are at the end)

pos.base()

Yields (a copy of) the underlying iterator

. . .

All standard iterator operations of the underlying iterator type

pos == std::default_sentinel

Yields whether the iterator is at the end

pos != std::default_sentinel

Yields whether the iterator is not at the end

Table 7.2. Operations of class std::counted_iterator<>

It is up to the programmer to ensure that:

The initial count is not higher than the iterator initially passed

The counted iterator does not increment more than count times

The counted iterator does not access elements beyond the count-th element (as usual, the position behind the last element is valid)

7.3.2std::common_iterator

Type std::common_iterator<> is used to harmonize the type of two iterators. It wraps the two iterators so that from the outside, both iterators have the same type. Internally, a value of one of the two types is stored (for this, the iterator typically uses a std::variant<>).

180

Chapter 7: Utilities for Ranges and Views

For example, traditional algorithms that take a begin iterator and an end iterator require that these iterators have the same type. If you have iterators of different types, you can use this type function be able for calling these algorithm:

algo(beg, end); // if this is an error due to different types

algo(std::common_iterator<decltype(beg),

decltype(end)>{beg}, // OK

std::common_iterator<decltype(beg),

decltype(end)>{end});

Note that it is a compile-time error if the types passed to common_iterator<> are the same. Thus, in generic code, you might have to call:

template<typename BegT, typename EndT> void callAlgo(BegT beg, EndT end)

{

if constexpr(std::same_as<BegT, EndT>) { algo(beg, end);

}

else {

algo(std::common_iterator<decltype(beg), decltype(end)>{beg}, std::common_iterator<decltype(beg), decltype(end)>{end});

}

}

A more convenient way to have the same effect is to use the common() range adaptor:

template<typename BegT, typename EndT> void callAlgo(BegT beg, EndT end)

{

auto v = std::views::common(std::ranges::subrange(beg,end)); algo(v.begin(), v.end());

}

7.3.3 std::default_sentinel

A default sentinel is an iterator that provides no operations at all. C++20 provides a corresponding object std::default_sentinel, which has type std::default_sentinel_t. It is provided in <iterator>. The type has no members:

namespace std {

class default_sentinel_t { };

inline constexpr default_sentinel_t default_sentinel{};

}

The type and the value are provided to serve as a dummy sentinel (end iterator), when an iterator knows its end without looking at the end iterator. For those iterators, a comparison with std::default_sentinel is defined but the default sentinel is never used. Instead, the iterator checks internally whether it is at the end (or how far it is from the end).

7.3 New Iterator and Sentinel Types

181

For example, counted iterators define an operator == for themselves with a default sentinel to perform checking whether they are at the end:

namespace std { template<std::input_or_output_iterator I> class counted_iterator {

...

friend constexpr bool operator==(const counted_iterator& p, std::default_sentinel_t) {

... // returns whether p is at the end

}

};

}

This allows code like this:

// iterate over the first five elements:

for (std::counted_iterator pos{coll.begin(), 5}; pos != std::default_sentinel;

++pos) {

std::cout << *pos << '\n';

}

The standard defines the following operations with default sentinels:

For std::counted_iterator:

Comparisons with operator == and !=

Computing the distance with operator -

std::views::counted() may create a subrange of a counted iterator and a default sentinel

For std::istream_iterator:

Default sentinels can be used as an initial value, which has the same effect as the default constructor

Comparisons with operator == and !=

For std::istreambuf_iterator:

Default sentinels can be used as an initial value, which has the same effect as the default constructor

Comparisons with operator == and !=

For std::ranges::basic_istream_view<>:

Istream views yield std::default_sentinel as end()

Istream view iterators can compare with operator == and !=

For std::ranges::take_view<>:

Take views may yield std::default_sentinel as end()

For std::ranges::split_view<>:

Split views may yield std::default_sentinel as end()

Split view iterators can compare with operator == and !=