Поделиться через


<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_offor_each_ncountcount_iffind_if_notfor_eachequalcopy_nall_ofmismatchcopy_ifnone_offindfind_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
beginC++20 Получите итератор к первому элементу в диапазоне.
cbeginC++20 Получите итератор к первому const элементу в диапазоне.
cendC++20 Получите sentinel в конце constдиапазона -qualified.
cdataC++20 const Получите указатель на первый элемент в непрерывном диапазоне.
crbeginC++20 Получите обратный const итератор до начала диапазона.
crendC++20 Получите sentinel в конце возвращаемого crbegin() значения.
dataC++20 Получите указатель на первый элемент в непрерывном диапазоне.
emptyC++20 Определите, является ли диапазон пустым.
endC++20 Получите sentinel в конце диапазона.
rbeginC++20 Получите обратный итератор до начала диапазона.
rendC++20 Получите обратный итератор в sentinel в конце диапазона.
sizeC++20 Получите размер диапазона в виде значения без знака.
ssizeC++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_tC++20 Определите, возвращается ли итератор для range диапазона, время существования которого завершено.
borrowed_subrange_tC++20 Определите, возвращается ли итератор для subrange подранга, время существования которого завершилось.
danglingC++20 Указывает, что возвращаемый итератор извеченного времени существования/subrangerangeссылки на него.range/subrange
iterator_tC++20 Возвращает тип итератора указанного типа диапазона.
range_difference_tC++20 Возвращает тип разницы типа итератора указанного диапазона.
range_reference_tC++20 Возвращает ссылочный тип итератора указанного диапазона.
range_rvalue_reference_tC++20 Возвращает ссылочный тип rvalue для итератора указанного диапазона. Другими словами, ссылочный тип rvalue элементов диапазона.
range_size_tC++20 Возвращает тип, используемый для отчета о размере указанного диапазона.
range_value_tC++20 Возвращает тип значения типа итератора указанного диапазона. Или другими словами, тип элементов в диапазоне.
sentinel_tC++20 Возвращает тип sentinel указанного диапазона.

Дополнительные сведения об этих шаблонах псевдонимов см. в разделе <ranges> "Шаблоны псевдонимов".

См. также

Функции <ranges>
<ranges> Концепции
Адаптеры диапазона
Справочник по файлам заголовков