<ranges>
На высоком уровне диапазон — это то, что можно итерировать. Диапазон представлен итератором, который отмечает начало диапазона и sentinel, который отмечает конец диапазона. Sentinel может быть таким же типом, как итератор начала, или может отличаться. Контейнеры, такие как vector
и list
, в стандартной библиотеке C++, являются диапазонами. Диапазон абстрагирует итераторы таким образом, чтобы упростить и усилить возможность использования стандартной библиотеки шаблонов (STL).
Алгоритмы STL обычно принимают итераторы, указывающие на часть коллекции, на которую они должны работать. Например, рассмотрим, как сортировать vector
с помощью std::sort()
. Вы передаете два итератора, которые помечают начало и конец vector
. Это обеспечивает гибкость, но передача итераторов в алгоритм является дополнительной работой, потому что вы, вероятно, просто хотите отсортировать всю вещь.
С диапазонами можно вызвать std::ranges::sort(myVector);
, который обрабатывается как если бы вы вызвали std::sort(myVector.begin(), myVector.end());
. В библиотеках диапазонов алгоритмы принимают диапазоны в качестве параметров (хотя они также могут принимать итераторы, если требуется). Они могут работать непосредственно с коллекциями. Примеры алгоритмов диапазона, доступных в <algorithm>
том числе copy
, any_of
for_each_n
count
count_if
find_if_not
for_each
equal
copy_n
all_of
mismatch
copy_if
none_of
find
find_if
и .
Но, возможно, самое важное преимущество диапазонов заключается в том, что вы можете создавать алгоритмы STL, которые работают на диапазонах в стиле, напоминающем функциональное программирование.
Пример диапазонов
Перед диапазонами, если вы хотите преобразовать элементы коллекции, которая соответствовала определенному критерию, необходимо ввести промежуточный шаг для хранения результатов между операциями. Например, если вы хотите создать вектор квадратов из элементов в другом векторе, который делится на три, можно написать примерно следующее:
std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::vector<int> intermediate, output;
std::copy_if(input.begin(), input.end(), std::back_inserter(intermediate), [](const int i) { return i%3 == 0; });
std::transform(intermediate.begin(), intermediate.end(), std::back_inserter(output), [](const int i) {return i*i; });
С помощью диапазонов можно выполнить то же самое, не нуждаясь в векторе intermediate
:
// requires /std:c++20
std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto output = input
| std::views::filter([](const int n) {return n % 3 == 0; })
| std::views::transform([](const int n) {return n * n; });
Помимо упрощения чтения, этот код позволяет избежать выделения памяти, необходимого для вектора и его содержимого intermediate
. Он также позволяет создавать две операции.
В приведенном выше коде каждый элемент, делимый по трем, объединяется с операцией на квадрат этого элемента. Символ канала (|
) объединяет операции и считывается слева направо.
Результат, output
сам по себе является типом диапазона, называемого представлением.
Представления
Представление — это упрощенный диапазон. Просмотр операций, таких как построение по умолчанию, перемещение и назначение, копирование строительства или назначения (если присутствует), уничтожение, начало и завершение выполняются в постоянное время независимо от количества элементов в представлении.
Представления создаются адаптерами диапазона, которые рассматриваются в следующем разделе. Дополнительные сведения о классах, реализующих различные представления, см. в разделе "Просмотр классов".
Способ отображения элементов в представлении зависит от адаптера диапазона, используемого для создания представления. В предыдущем примере адаптер диапазона принимает диапазон и возвращает представление элементов, делимых на три. Базовый диапазон не изменяется.
Представления являются компонуемыми, что является мощным. В предыдущем примере представление векторных элементов, делимых на три, объединяется с представлением, которое квадратирует эти элементы.
Элементы представления оцениваются лениво. То есть преобразования, которые применяются к каждому элементу в представлении, не оцениваются, пока не запрашивается элемент. Например, если вы запускаете следующий код в отладчике и помещаете точку останова в строки auto divisible_by_three = ...
, и auto square = ...
вы увидите, что вы попали в divisible_by_three
лямбда-точку останова, так как каждый элемент в input
ней проверяется для разбиения на три. square
Лямбда-точка останова будет достигнута, так как элементы, разделенные тремя, квадраты.
// requires /std:c++20
#include <ranges>
#include <vector>
#include <iostream>
int main()
{
std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto divisible_by_three = [](const int n) {return n % 3 == 0; };
auto square = [](const int n) {return n * n; };
auto x = input | std::views::filter(divisible_by_three)
| std::views::transform(square);
for (int i : x)
{
std::cout << i << '\n';
}
return 0;
}
Дополнительные сведения о представлениях см. в разделе <ranges>
"Просмотр классов".
Адаптеры диапазона
Адаптеры диапазона принимают диапазон и создают представление. Адаптеры диапазона создают неясные вычисляющиеся представления. То есть вы не несете затраты на преобразование каждого элемента в диапазоне для создания представления. Вы оплачиваете только затраты на обработку элемента в представлении при доступе к элементу.
В предыдущем примере filter
адаптер диапазона создает представление с именем input
, которое содержит элементы, делимые на три. Адаптер transform
диапазона принимает представление элементов, разделенных на три, и создает представление этих элементов в квадрате.
Адаптеры диапазона можно объединить (составить), что является сердцем мощности и гибкости диапазонов. Создание адаптеров диапазона позволяет преодолеть проблему, которую предыдущие алгоритмы STL не легко компостировать.
Дополнительные сведения о создании представлений см. в разделе "Адаптеры диапазона".
Алгоритмы диапазона
Некоторые алгоритмы диапазона принимают аргумент диапазона. Например, std::ranges::sort(myVector);
.
Алгоритмы диапазона почти идентичны соответствующим алгоритмам пар итератора в std
пространстве имен. Разница заключается в том, что они имеют ограничения, применяемые концепцией, и они принимают аргументы диапазона или несколько пар аргументов итератора-sentinel. Они могут работать непосредственно в контейнере и легко объединяться.
<ranges>
Функции
Следующие функции используются для создания итераторов и sentinels для диапазонов и получения размера диапазона.
Function | Description |
---|---|
begin C++20 |
Получите итератор к первому элементу в диапазоне. |
cbegin C++20 |
Получите итератор к первому const элементу в диапазоне. |
cend C++20 |
Получите sentinel в конце const диапазона -qualified. |
cdata C++20 |
const Получите указатель на первый элемент в непрерывном диапазоне. |
crbegin C++20 |
Получите обратный const итератор до начала диапазона. |
crend C++20 |
Получите sentinel в конце возвращаемого crbegin() значения. |
data C++20 |
Получите указатель на первый элемент в непрерывном диапазоне. |
empty C++20 |
Определите, является ли диапазон пустым. |
end C++20 |
Получите sentinel в конце диапазона. |
rbegin C++20 |
Получите обратный итератор до начала диапазона. |
rend C++20 |
Получите обратный итератор в sentinel в конце диапазона. |
size C++20 |
Получите размер диапазона в виде значения без знака. |
ssize C++20 |
Получите размер диапазона в виде подписанного значения. |
Дополнительные сведения см. в разделе <ranges>
"Функции".
Основные понятия диапазона
Способ итерации элементов диапазона зависит от его базового типа итератора. Диапазоны используют понятия C++, определяющие, какой итератор они поддерживают.
В C++20, чтобы сказать, что концепция X уточнения концепции Y означает, что все, что удовлетворяет концепции Y, также удовлетворяет концепции X. Например, автомобиль, автобус и грузовик все уточнения транспортного средства.
Некоторые понятия диапазона отражают иерархию категорий итератора. В следующей таблице перечислены понятия диапазона, а также типы контейнеров, к которым они могут применяться.
Концепция диапазона | Description | Поддерживаемые контейнеры |
---|---|---|
std::ranges::output_range |
Может выполнять итерацию вперед. | |
std::ranges::input_range |
Может выполнять итерацию по крайней мере один раз. | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset basic_istream_view |
std::ranges::forward_range |
Может выполнять итерацию от начала до конца более одного раза. | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset |
std::ranges::bidirectional_range |
Может выполнять итерацию вперед и назад несколько раз. | std::list std::map std::multimap std::multiset std::set |
std::ranges::random_access_range |
Может получить доступ к произвольному элементу (в постоянном времени) с помощью [] оператора. |
std::deque |
std::ranges::contiguous_range |
Элементы хранятся в памяти последовательно. | std::array std::string std::vector |
Дополнительные сведения об этих понятиях см <ranges>
. в концепциях .
<ranges>
Шаблоны псевдонимов
Следующие шаблоны псевдонимов определяют типы итераторов и sentinels для диапазона:
Шаблон псевдонима | Description |
---|---|
borrowed_iterator_t C++20 |
Определите, возвращается ли итератор для range диапазона, время существования которого завершено. |
borrowed_subrange_t C++20 |
Определите, возвращается ли итератор для subrange подранга, время существования которого завершилось. |
dangling C++20 |
Указывает, что возвращаемый итератор извеченного времени существования/subrange range ссылки на него.range /subrange |
iterator_t C++20 |
Возвращает тип итератора указанного типа диапазона. |
range_difference_t C++20 |
Возвращает тип разницы типа итератора указанного диапазона. |
range_reference_t C++20 |
Возвращает ссылочный тип итератора указанного диапазона. |
range_rvalue_reference_t C++20 |
Возвращает ссылочный тип rvalue для итератора указанного диапазона. Другими словами, ссылочный тип rvalue элементов диапазона. |
range_size_t C++20 |
Возвращает тип, используемый для отчета о размере указанного диапазона. |
range_value_t C++20 |
Возвращает тип значения типа итератора указанного диапазона. Или другими словами, тип элементов в диапазоне. |
sentinel_t C++20 |
Возвращает тип sentinel указанного диапазона. |
Дополнительные сведения об этих шаблонах псевдонимов см. в разделе <ranges>
"Шаблоны псевдонимов".
См. также
Функции <ranges>
<ranges>
Концепции
Адаптеры диапазона
Справочник по файлам заголовков