Compartir a través de


Conceptos <ranges>

Los conceptos son una característica del lenguaje C++20 que restringe los parámetros de plantilla en tiempo de compilación. Ayudan a evitar la creación de instancias de plantilla incorrectas, especificar los requisitos de argumento de plantilla en un formulario legible y proporcionar más errores de compilador relacionados con la plantilla concisa.

Considere el ejemplo siguiente, que define un concepto para evitar la creación de instancias de una plantilla con un tipo que no admite la división:

// 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 
}

Al pasar el modificador /diagnostics:caret del compilador a visual Studio 2022, versión preliminar 17.4 o posterior, el error que el concepto dividable<char*> evaluado como false apuntará directamente al requisito (a / b) de expresión que produjo un error.

Los conceptos de intervalo se definen en el std::ranges espacio de nombres y se declaran en el archivo de <ranges> encabezado. Se usan en las declaraciones de adaptadores de rango, vistas, etc.

Hay seis categorías de intervalos. Están relacionados con las categorías de iteradores enumerados en <iterator> conceptos. Para aumentar la capacidad, las categorías son:

Concepto de intervalo Descripción
output_range
input_range
Especifica un intervalo en el que puede escribir.
Especifica un intervalo que se puede leer de una vez.
forward_range Especifica un intervalo que se puede leer (y posiblemente escribir) varias veces.
bidirectional_range Especifica un intervalo que se puede leer y escribir tanto hacia delante como hacia atrás.
random_access_range Especifica un intervalo que puede leer y escribir por índice.
contiguous_range Especifica un intervalo cuyos elementos son secuenciales en la memoria, tienen el mismo tamaño y se puede tener acceso a él mediante la aritmética de puntero.

En la tabla anterior, los conceptos se enumeran en orden de aumentar la capacidad. Un intervalo que cumple los requisitos de un concepto generalmente cumple los requisitos de los conceptos de las filas que lo preceden. Por ejemplo, un random_access_range tiene la funcionalidad de , bidirectional_rangeforward_range, input_rangey output_range. La excepción es input_range, que no se puede escribir en , por lo que no tiene las funcionalidades de output_range.

Diagrama de la jerarquía de iteradores de intervalos. input_range y output_range son los iteradores más básicos. forward_range es siguiente y refina tanto input_range como output_range. bidirectional_range refina forward_range. random_access_range refina bidirectional_range. Por último, contiguous_range refina random_access_range

Otros conceptos de rango incluyen:

Concepto de intervalo Descripción
rangeC++20 Especifica un tipo que proporciona un iterador y un sentinel.
borrowed_rangeC++20 Especifica que la duración de los iteradores del intervalo no está vinculada a la duración del intervalo.
common_rangeC++20 Especifica que el tipo del iterador del intervalo y el tipo del centinela del intervalo son los mismos.
Simple_ViewC++20 No es un concepto oficial definido como parte de la biblioteca estándar, pero se usa como un concepto auxiliar en algunas interfaces.
sized_rangeC++20 Especifica un intervalo que puede proporcionar su número de elementos de forma eficaz.
viewC++20 Especifica un tipo que tiene una construcción, asignación y destrucción eficientes (tiempo constante).
viewable_rangeC++20 Especifica un tipo que es una vista o se puede convertir en uno.

bidirectional_range

Un bidirectional_range admite la lectura y escritura del intervalo hacia delante y hacia atrás.

template<class T>
concept bidirectional_range =
    forward_range<T> && bidirectional_iterator<iterator_t<T>>;

Parámetros

T
Tipo que se va a probar para ver si es .bidirectional_range

Comentarios

Este tipo de intervalo admite bidirectional_iterator o mayor. tiene bidirectional_iterator las funcionalidades de , forward_iteratorpero también puede iterar hacia atrás.

Algunos ejemplos de bidirectional_range son std::set, std::vectory std::list.

borrowed_range

Un tipo modela borrowed_range si la validez de los iteradores que obtiene del objeto puede sobrevivir a la duración del objeto. Es decir, los iteradores de un intervalo se pueden usar incluso cuando el intervalo ya no existe.

template<class T>
concept borrowed_range =
    range<T> &&
    (is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);

Parámetros

T
Tipo que se va a probar para ver si es .borrowed_range

Comentarios

La duración de un intervalo rvalue puede terminar después de una llamada de función si los modelos borrowed_range de intervalo o no. Si es , borrowed_rangees posible que pueda seguir usando los iteradores con un comportamiento bien definido, independientemente de cuándo finalice la duración del intervalo.

Los casos en los que esto no es true son, por ejemplo, para contenedores como vector o list porque cuando finaliza la duración del contenedor, los iteradores harían referencia a elementos que se han destruido.

Puede seguir usando los iteradores de un borrowed_range, por ejemplo, para un view como iota_view<int>{0, 42} cuyos iteradores están sobre el conjunto de valores que no están sujetos a ser destruidos porque se generan a petición.

common_range

El tipo del iterador para un common_range es el mismo que el tipo del centinela. Es decir, begin() y end() devuelve el mismo tipo.

template<class T>
concept common_range =
   ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;

Parámetros

T
Tipo que se va a probar para ver si es .common_range

Comentarios

Obtener el tipo de std::ranges::begin() y std::ranges::end() es importante para los algoritmos que calculan la distancia entre dos iteradores y para los algoritmos que aceptan intervalos indicados por pares de iterador.

Los contenedores estándar (por ejemplo, vector) cumplen los requisitos de common_range.

contiguous_range

Los elementos de se contiguous_range almacenan secuencialmente en la memoria y se puede acceder a ellos mediante la aritmética de puntero. Por ejemplo, una matriz es .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>>>;};

Parámetros

T
Tipo que se va a probar para ver si es .contiguous_range

Comentarios

Se contiguous_range puede acceder a un objeto aritmético de puntero porque los elementos se colocan secuencialmente en la memoria y tienen el mismo tamaño. Este tipo de intervalo admite continguous_iterator, que es el más flexible de todos los iteradores.

Algunos ejemplos de contiguous_range son std::array, std::vectory std::string.

Ejemplo: contiguous_range

En el ejemplo siguiente se muestra el uso de la aritmética de puntero para acceder a :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

Un forward_range admite la lectura (y posiblemente escribiendo) el intervalo varias veces.

template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;

Parámetros

T
Tipo que se va a probar para ver si es .forward_range

Comentarios

Este tipo de intervalo admite forward_iterator o mayor. Una forward_iterator puede iterar en un intervalo varias veces.

input_range

es input_range un intervalo que se puede leer de una vez.

template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;

Parámetros

T
Tipo que se va a probar para ver si es .input_range

Comentarios

Cuando un tipo cumple los requisitos de input_range:

  • La ranges::begin() función devuelve un input_iterator. Llamar begin() a más de una vez en un da como resultado un input_range comportamiento indefinido.
  • Puede desreferenciar un valor input_iterator repetidamente, que produce el mismo valor cada vez. No input_range es un paso múltiple. Al incrementar un iterador, se invalidan las copias.
  • Se puede usar con ranges::for_each.
  • Admite input_iterator o mayor.

output_range

Es output_range un intervalo en el que se puede escribir.

template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;

Parámetros

R
Tipo del rango.

T
Tipo de los datos que se van a escribir en el intervalo.

Comentarios

El significado de output_iterator<iterator_t<R>, T> es que el tipo proporciona un iterador que puede escribir valores de tipo T en un intervalo de tipo R. En otras palabras, admite output_iterator o mayor.

random_access_range

Un random_access_range puede leer o escribir un intervalo por índice.

template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;

Parámetros

T
Tipo que se va a probar para ver si es .sized_range

Comentarios

Este tipo de intervalo admite random_access_iterator o mayor. tiene random_access_range las funciones de , input_rangeoutput_range, forward_rangey bidirectional_range. Se random_access_range puede ordenar un .

Algunos ejemplos de random_access_range son std::vector, std::arrayy std::deque.

range

Define los requisitos que debe cumplir un tipo para ser .range range proporciona un iterador y un centinela para que pueda iterar sobre sus elementos.

template<class T>
concept range = requires(T& rg)
{
  ranges::begin(rg);
  ranges::end(rg);
};

Parámetros

T
Tipo que se va a probar para ver si es .range

Comentarios

Los requisitos de son range :

  • Se puede iterar mediante std::ranges::begin() y std::ranges::end()
  • ranges::begin()y ranges::end() se ejecutan en tiempo constante amortizado y no modifican .range El tiempo constante amortizado no significa O(1), pero que el costo medio de una serie de llamadas, incluso en el peor de los casos, es O(n) en lugar de O(n^2) o peor.
  • [ranges::begin(), ranges::end()) denota un intervalo válido.

Simple_View

Un Simple_View es un concepto de solo exposición utilizado en algunas ranges interfaces. No se define en la biblioteca. Solo se usa en la especificación para ayudar a describir el comportamiento de algunos adaptadores de rango.

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>>;

Parámetros

V
Tipo que se va a probar para ver si es .Simple_View

Comentarios

Una vista V es si Simple_View todas las siguientes son verdaderas:

  • V es una vista
  • const V es un intervalo
  • Tanto como v const V tienen los mismos tipos de iterador y centinela.

sized_range

A sized_range proporciona el número de elementos del intervalo en tiempo constante amortizado.

template<class T>
  concept sized_range = range<T> &&
    requires(T& t) { ranges::size(t); };

Parámetros

T
Tipo que se va a probar para ver si es .sized_range

Comentarios

Los requisitos de un sized_range objeto son que llaman ranges::size a en él:

  • No modifica el intervalo.
  • Devuelve el número de elementos en tiempo constante amortizado. El tiempo constante amortizado no significa O(1), pero que el costo medio de una serie de llamadas, incluso en el peor de los casos, es O(n) en lugar de O(n^2) o peor.

Algunos ejemplos de sized_range son std::list y std::vector.

Ejemplo: sized_range

En el ejemplo siguiente se muestra que un vector de int es :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

Tiene view operaciones constantes de construcción, asignación y destrucción de tiempo, independientemente del número de elementos que tiene. No es necesario que las vistas sean construyebles o asignables de copia, pero, si son, esas operaciones también se deben ejecutar en tiempo constante.

Debido al requisito de tiempo constante, puede componer de forma eficaz las vistas. Por ejemplo, dado un vector de int denominado input, una función que determina si un número es divisible por tres y una función que cuadrado un número, la instrucción auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square); genera eficazmente una vista que contiene los cuadrados de los números de entrada que son divisibles en tres. La conexión de vistas junto con | se conoce como redacción de las vistas. Si un tipo satisface el view concepto, se puede componer de forma eficaz.

template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;

Parámetros

T
Tipo que se va a probar para ver si es una vista.

Comentarios

El requisito esencial que hace que una vista se pueda componer es que es barato mover o copiar. Esto se debe a que la vista se mueve o copia cuando se compone con otra vista. Debe ser un rango móvil.

ranges::enable_view<T> es un rasgo que se usa para reclamar la conformidad con los requisitos semánticos del view concepto. Un tipo puede optar por:

  • pública e inequívocamente derivada de una especialización de ranges::view_interface
  • pública e inequívocamente derivada de la clase ranges::view_basevacía , o
  • especializado ranges::enable_view<T> en true

Se prefiere la opción 1 porque view_interface también proporciona la implementación predeterminada que guarda algún código reutilizable que tiene que escribir.

Si se produce un error, la opción 2 es un poco más sencilla que la opción 3.

La ventaja de la opción 3 es que es posible sin cambiar la definición del tipo.

viewable_range

Un viewable_range es un tipo que es una vista o se puede convertir en uno.

template<class T>
  concept viewable_range =
    range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);

Parámetros

T
Tipo que se va a probar para ver si es una vista o se puede convertir en una.

Comentarios

Use std::ranges::views::all() para convertir un intervalo en una vista.

Consulte también

<ranges>
Adaptadores de rango
Ver clases