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_range
forward_range
, input_range
y output_range
. La excepción es input_range
, que no se puede escribir en , por lo que no tiene las funcionalidades de output_range
.
Otros conceptos de rango incluyen:
Concepto de intervalo | Descripción |
---|---|
range C++20 |
Especifica un tipo que proporciona un iterador y un sentinel. |
borrowed_range C++20 |
Especifica que la duración de los iteradores del intervalo no está vinculada a la duración del intervalo. |
common_range C++20 |
Especifica que el tipo del iterador del intervalo y el tipo del centinela del intervalo son los mismos. |
Simple_View C++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_range C++20 |
Especifica un intervalo que puede proporcionar su número de elementos de forma eficaz. |
view C++20 |
Especifica un tipo que tiene una construcción, asignación y destrucción eficientes (tiempo constante). |
viewable_range C++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_iterator
pero también puede iterar hacia atrás.
Algunos ejemplos de bidirectional_range
son std::set
, std::vector
y 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_range
es 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::vector
y 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 uninput_iterator
. Llamarbegin()
a más de una vez en un da como resultado uninput_range
comportamiento indefinido. - Puede desreferenciar un valor
input_iterator
repetidamente, que produce el mismo valor cada vez. Noinput_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_range
output_range
, forward_range
y bidirectional_range
. Se random_access_range
puede ordenar un .
Algunos ejemplos de random_access_range
son std::vector
, std::array
y 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()
ystd::ranges::end()
ranges::begin()
yranges::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 vistaconst 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_base
vacía , o - especializado
ranges::enable_view<T>
entrue
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.