<ranges>
Концепции
Основные понятия — это функция языка C++20, которая ограничивает параметры шаблона во время компиляции. Они помогают предотвратить неправильное создание экземпляра шаблона, указать требования к аргументам шаблона в доступной для чтения форме и предоставить более краткие ошибки, связанные с компилятором.
Рассмотрим следующий пример, который определяет концепцию, чтобы предотвратить создание экземпляра шаблона с типом, который не поддерживает разделение:
// requires /std:c++20 or later
#include <iostream>
// Definition of dividable concept which requires
// that arguments a & b of type T support division
template <typename T>
concept dividable = requires (T a, T b)
{
a / b;
};
// Apply the concept to a template.
// The template will only be instantiated if argument T supports division.
// This prevents the template from being instantiated with types that don't support division.
// This could have been applied to the parameter of a template function, but because
// most of the concepts in the <ranges> library are applied to classes, this form is demonstrated.
template <class T> requires dividable<T>
class DivideEmUp
{
public:
T Divide(T x, T y)
{
return x / y;
}
};
int main()
{
DivideEmUp<int> dividerOfInts;
std::cout << dividerOfInts.Divide(6, 3); // outputs 2
// The following line will not compile because the template can't be instantiated
// with char* because char* can be divided
DivideEmUp<char*> dividerOfCharPtrs; // compiler error: cannot deduce template arguments
}
При передаче компилятора в /diagnostics:caret
Visual Studio 2022 версии 17.4 предварительной версии 4 или более поздней версии ошибка, которая оценивается как false, указывает непосредственно на требование dividable<char*>
(a / b)
выражения, которое завершилось сбоем.
Понятия диапазона определяются в std::ranges
пространстве имен и объявляются в файле заголовка <ranges>
. Они используются в объявлениях адаптеров диапазона, представлений и т. д.
Существует шесть категорий диапазонов. Они связаны с категориями итераторов, перечисленных в <iterator>
концепциях. В порядке увеличения возможностей категории:
Концепция диапазона | Description |
---|---|
output_range input_range |
Задает диапазон, в который можно написать. Указывает диапазон, который можно прочитать один раз. |
forward_range |
Задает диапазон, который можно читать (и, возможно, записывать) несколько раз. |
bidirectional_range |
Задает диапазон, который можно считывать и записывать как вперед, так и назад. |
random_access_range |
Указывает диапазон, который можно читать и записывать по индексу. |
contiguous_range |
Задает диапазон, элементы которого являются последовательными в памяти, имеют одинаковый размер и могут быть доступны с помощью арифметики указателя. |
В приведенной выше таблице основные понятия перечислены в порядке увеличения возможностей. Диапазон, соответствующий требованиям концепции, обычно соответствует требованиям концепций в строках, предшествующих ему. Например, у объекта random_access_range
есть возможность bidirectional_range
, forward_range
input_range
и output_range
. Исключение заключается в input_range
том, что не может быть записано, поэтому у него нет возможностей output_range
.
К другим понятиям диапазона относятся:
Концепция диапазона | Description |
---|---|
range C++20 |
Указывает тип, предоставляющий итератор и sentinel. |
borrowed_range C++20 |
Указывает, что время существования итераторов диапазона не привязано к времени существования диапазона. |
common_range C++20 |
Указывает, что тип итератора диапазона и тип sentinel диапазона совпадают. |
Simple_View C++20 |
Не официальная концепция, определенная как часть стандартной библиотеки, но используется в качестве вспомогательной концепции для некоторых интерфейсов. |
sized_range C++20 |
Задает диапазон, который может эффективно предоставлять его количество элементов. |
view C++20 |
Указывает тип, имеющий эффективное (постоянное время) перемещение, назначение и уничтожение. |
viewable_range C++20 |
Указывает тип, который является представлением или может быть преобразован в один. |
bidirectional_range
Поддерживает bidirectional_range
чтение и запись диапазона вперед и назад.
template<class T>
concept bidirectional_range =
forward_range<T> && bidirectional_iterator<iterator_t<T>>;
Параметры
T
Тип для проверки, чтобы узнать, является bidirectional_range
ли это .
Замечания
Этот тип диапазона поддерживает bidirectional_iterator
или больше.
A имеет возможности объекта bidirectional_iterator
forward_iterator
, но также может выполнять итерацию назад.
Ниже приведены некоторые примеры bidirectional_range
std::set
: , std::vector
и std::list
.
borrowed_range
Модели borrowed_range
типов, если допустимость итераторов, полученных от объекта, может перевыполнить время существования объекта. То есть итераторы для диапазона можно использовать даже в том случае, если диапазон больше не существует.
template<class T>
concept borrowed_range =
range<T> &&
(is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);
Параметры
T
Тип для проверки, чтобы узнать, является borrowed_range
ли это .
Замечания
Время существования диапазона rvalue может завершиться после вызова функции, независимо от того borrowed_range
, модели диапазона или нет. Если это borrowed_range
так, вы можете продолжать использовать итераторы с хорошо определенным поведением независимо от того, когда время существования диапазона заканчивается.
В случаях, когда это не так, например для контейнеров, таких vector
как или list
из-за окончания времени существования контейнера, итераторы будут ссылаться на элементы, которые были уничтожены.
Вы можете продолжать использовать итераторы для borrowed_range
например, например, например, для view
iota_view<int>{0, 42}
того, чьи итераторы имеют набор значений, которые не подвергаются уничтожению, так как они создаются по требованию.
common_range
Тип итератора для объекта common_range
совпадает с типом sentinel. То есть begin()
и end()
возвращает тот же тип.
template<class T>
concept common_range =
ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;
Параметры
T
Тип для проверки, чтобы узнать, является common_range
ли это .
Замечания
Получение типа от std::ranges::begin()
и std::ranges::end()
важно для алгоритмов, которые вычисляют расстояние между двумя итераторами, и для алгоритмов, которые принимают диапазоны, обозначаемые парами итератора.
Стандартные контейнеры (например, vector
) соответствуют требованиям common_range
.
contiguous_range
Элементы объекта contiguous_range
хранятся последовательно в памяти и могут быть доступны с помощью арифметики указателя. Например, массив является массивом contiguous_range
.
template<class T>
concept contiguous_range =
random_access_range<T> && contiguous_iterator<iterator_t<T>> &&
requires(T& t) {{ ranges::data(t) } -> same_as<add_pointer_t<range_reference_t<T>>>;};
Параметры
T
Тип для проверки, чтобы узнать, является contiguous_range
ли это .
Замечания
Доступ contiguous_range
к ней можно получить с помощью арифметики указателя, так как элементы выкладываются последовательно в памяти и имеют одинаковый размер. Этот вид диапазона поддерживает continguous_iterator
, что является наиболее гибким из всех итераторов.
Ниже приведены некоторые примеры contiguous_range
std::array
: , std::vector
и std::string
.
Пример: contiguous_range
В следующем примере показано использование арифметики указателя для доступа к :contiguous_range
// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>
int main()
{
// Show that vector is a contiguous_range
std::vector<int> v = {0,1,2,3,4,5};
std::cout << std::boolalpha << std::ranges::contiguous_range<decltype(v)> << '\n'; // outputs true
// Show that pointer arithmetic can be used to access the elements of a contiguous_range
auto ptr = v.data();
ptr += 2;
std::cout << *ptr << '\n'; // outputs 2
}
true
2
forward_range
Поддерживает forward_range
чтение (и, возможно, запись) диапазона несколько раз.
template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;
Параметры
T
Тип для проверки, чтобы узнать, является forward_range
ли это .
Замечания
Этот тип диапазона поддерживает forward_iterator
или больше. Может forward_iterator
выполнять итерацию по диапазону несколько раз.
input_range
Это input_range
диапазон, который можно считывать один раз.
template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;
Параметры
T
Тип для проверки, чтобы узнать, является input_range
ли это .
Замечания
Если тип соответствует требованиям input_range
:
- Функция
ranges::begin()
возвращаетinput_iterator
значение . Вызовbegin()
нескольких раз в результатах неопределенногоinput_range
поведения. - Вы можете повторно разыменовыть
input_iterator
значение, которое каждый раз дает одно и то же значение. Неinput_range
является многопроходным. Приращение итератора делает недействительными все копии. - Его можно использовать с
ranges::for_each
. - Он поддерживает
input_iterator
или больше.
output_range
Это output_range
диапазон, в который можно написать.
template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;
Параметры
R
Тип диапазона.
T
Тип данных для записи в диапазон.
Замечания
Смысл output_iterator<iterator_t<R>, T>
заключается в том, что тип предоставляет итератор, который может записывать значения типа T
в диапазон типа R
. Другими словами, он поддерживает output_iterator
или больше.
random_access_range
Может random_access_range
считывать или записывать диапазон по индексу.
template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;
Параметры
T
Тип для проверки, чтобы узнать, является sized_range
ли это .
Замечания
Этот тип диапазона поддерживает random_access_iterator
или больше. A random_access_range
имеет возможности input_range
, и output_range
forward_range
bidirectional_range
. A random_access_range
можно сортировать.
Ниже приведены некоторые примеры random_access_range
std::vector
: , std::array
и std::deque
.
range
Определяет требования, которые должны соответствовать типу range
. Предоставляет range
итератор и sentinel, чтобы вы могли выполнять итерацию по его элементам.
template<class T>
concept range = requires(T& rg)
{
ranges::begin(rg);
ranges::end(rg);
};
Параметры
T
Тип для проверки, чтобы узнать, является range
ли это .
Замечания
Требования к a range
:
- Его можно итерировать с помощью
std::ranges::begin()
иstd::ranges::end()
ranges::begin()
иranges::end()
выполните амортизированную константу и не изменяйте .range
Амортизированное постоянное время не означает O(1), но что средняя стоимость по серии вызовов, даже в худшем случае, является O(n), а не O(n^2) или хуже.[ranges::begin(), ranges::end())
обозначает допустимый диапазон.
Simple_View
А Simple_View
— это концепция, используемая только для некоторых ranges
интерфейсов. Он не определен в библиотеке. Он используется только в спецификации для описания поведения некоторых адаптеров диапазона.
template<class V>
concept Simple_View = // exposition only
ranges::view<V> && ranges::range<const V> &&
std::same_as<std::ranges::iterator_t<V>, std::ranges::iterator_t<const V>> &&
std::same_as<std::ranges::sentinel_t<V>, std::ranges::sentinel_t<const V>>;
Параметры
V
Тип для проверки, чтобы узнать, является Simple_View
ли это .
Замечания
Представление — это Simple_View
представлениеV
, если все из следующих значений имеют значение true:
V
— это представлениеconst V
— это диапазон- Оба
v
иconst V
имеют одинаковые итераторы и типы sentinel.
sized_range
A sized_range
предоставляет количество элементов в диапазоне в амортизированном времени константы.
template<class T>
concept sized_range = range<T> &&
requires(T& t) { ranges::size(t); };
Параметры
T
Тип для проверки, чтобы узнать, является sized_range
ли это .
Замечания
Требования к немуsized_range
:ranges::size
- Не изменяет диапазон.
- Возвращает количество элементов в амортизированном времени константы. Амортизированное постоянное время не означает O(1), но что средняя стоимость по серии вызовов, даже в худшем случае, является O(n), а не O(n^2) или хуже.
Ниже приведены std::list
std::vector
некоторые примерыsized_range
.
Пример: sized_range
В следующем примере показано, что а vector
int
) — это sized_range
:
// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>
int main()
{
std::cout << std::boolalpha << std::ranges::sized_range<std::vector<int>> << '\n'; // outputs "true"
}
view
Имеет view
постоянное перемещение времени, создание, назначение и уничтожение операций независимо от количества элементов, которые у него есть. Представления не должны быть конструкторными или назначаемыми для копирования, но если они есть, эти операции также должны выполняться в постоянное время.
Из-за необходимости постоянного времени можно эффективно создавать представления. Например, при векторе int
вызываемой input
функции, которая определяет, является ли число делимым на три, а функция, которая квадратирует число, оператор auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square);
эффективно создает представление, содержащее квадраты чисел во входных данных, делимое на три. Соединение представлений вместе с |
ним называется создание представлений. Если тип удовлетворяет view
концепции, его можно создать эффективно.
template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;
Параметры
T
Тип для проверки, чтобы узнать, является ли это представлением.
Замечания
Важное требование, которое делает представление компонуемым, заключается в том, что это дешево для перемещения и копирования. Это связано с тем, что представление перемещается или копируется при создании с другим представлением. Он должен быть перемещаемым диапазоном.
ranges::enable_view<T>
является признаком, используемым для утверждения соответствия семантических требований view
концепции. Тип может отказаться от:
- публично и однозначно производный от специализации
ranges::view_interface
- публично и однозначно производный от пустого класса
ranges::view_base
или - специализированный
ranges::enable_view<T>
true
Вариант 1 предпочтителен, так как view_interface
также предоставляет реализацию по умолчанию, которая сохраняет некоторый стандартный код, который необходимо написать.
Сбой, вариант 2 немного проще, чем вариант 3.
Преимущество варианта 3 заключается в том, что это возможно без изменения определения типа.
viewable_range
Это viewable_range
тип, который является представлением или может быть преобразован в один.
template<class T>
concept viewable_range =
range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);
Параметры
T
Тип для проверки, чтобы узнать, является ли оно представлением или может быть преобразовано в одно.
Замечания
Используется std::ranges::views::all()
для преобразования диапазона в представление.