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


<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_rangeinput_rangeи output_range. Исключение заключается в input_rangeтом, что не может быть записано, поэтому у него нет возможностей output_range.

Схема иерархии итератора диапазонов. input_range и output_range являются самыми основными итераторами. forward_range будет следующим и уточнением как input_range, так и output_range. bidirectional_range уточнения forward_range. random_access_range уточнения bidirectional_range. Наконец, contiguous_range уточнения random_access_range

К другим понятиям диапазона относятся:

Концепция диапазона Description
rangeC++20 Указывает тип, предоставляющий итератор и sentinel.
borrowed_rangeC++20 Указывает, что время существования итераторов диапазона не привязано к времени существования диапазона.
common_rangeC++20 Указывает, что тип итератора диапазона и тип sentinel диапазона совпадают.
Simple_ViewC++20 Не официальная концепция, определенная как часть стандартной библиотеки, но используется в качестве вспомогательной концепции для некоторых интерфейсов.
sized_rangeC++20 Задает диапазон, который может эффективно предоставлять его количество элементов.
viewC++20 Указывает тип, имеющий эффективное (постоянное время) перемещение, назначение и уничтожение.
viewable_rangeC++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_rangeforward_rangebidirectional_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() для преобразования диапазона в представление.

См. также

<ranges>
Адаптеры диапазона
Просмотр классов