<ranges>
En un nivel alto, un rango es algo que se puede iterar. Un intervalo se representa mediante un iterador que marca el principio del intervalo y un centinela que marca el final del intervalo. El sentinel puede ser el mismo tipo que el iterador inicial o puede ser diferente. Los contenedores, como vector
y list
, en la biblioteca estándar de C++ son intervalos. Un intervalo abstrae los iteradores de una manera que simplifica y amplía la capacidad de uso de la Biblioteca de plantillas estándar (STL).
Normalmente, los algoritmos STL toman iteradores que apuntan a la parte de la colección en la que deben operar. Por ejemplo, considere cómo ordenar un vector
mediante std::sort()
. Se pasan dos iteradores que marcan el principio y el final de .vector
Esto proporciona flexibilidad, pero pasar los iteradores al algoritmo es un trabajo adicional porque probablemente solo quiera ordenar todo.
Con intervalos, puede llamar a std::ranges::sort(myVector);
, que se trata como si llamara std::sort(myVector.begin(), myVector.end());
a . En las bibliotecas de intervalos, los algoritmos toman intervalos como parámetros (aunque también pueden tomar iteradores, si lo desea). Pueden operar directamente en colecciones. Algunos ejemplos de algoritmos de intervalo disponibles en incluyen , , any_of
copy_if
find_if
all_of
copy_n
none_of
find
, for_each
count_if
for_each_n
find_if_not
count
, equal
y .mismatch
copy
<algorithm>
Pero quizás la ventaja más importante de los rangos es que puede componer algoritmos STL que operan en intervalos en un estilo que recuerda a la programación funcional.
Ejemplo de intervalos
Antes de los intervalos, si desea transformar los elementos de una colección que cumplen un criterio determinado, debe introducir un paso intermedio para contener los resultados entre operaciones. Por ejemplo, si desea crear un vector de cuadrados a partir de los elementos de otro vector que son divisibles por tres, podría escribir algo parecido a:
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; });
Con los intervalos, puede lograr lo mismo sin necesidad del vector 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; });
Además de ser más fácil de leer, este código evita la asignación de memoria necesaria para el intermediate
vector y su contenido. También permite crear dos operaciones.
En el código anterior, cada elemento divisible por tres se combina con una operación para cuadrado ese elemento. El símbolo de canalización (|
) encadena las operaciones y se lee de izquierda a derecha.
El resultado, output
, es en sí mismo un tipo de intervalo denominado vista.
Vistas
Una vista es un intervalo ligero. Vea las operaciones, como la construcción predeterminada, la construcción o la asignación de movimiento, la construcción o asignación de copia (si está presente), la destrucción, el inicio y el fin de todo se producen en tiempo constante, independientemente del número de elementos de la vista.
Las vistas se crean mediante adaptadores de intervalo, que se describen en la sección siguiente. Para obtener más información sobre las clases que implementan varias vistas, vea Ver clases.
La forma en que aparecen los elementos de la vista depende del adaptador de intervalo que use para crear la vista. En el ejemplo anterior, un adaptador de rango toma un rango y devuelve una vista de los elementos divisibles por tres. El intervalo subyacente no cambia.
Las vistas se pueden componer, lo que es eficaz. En el ejemplo anterior, la vista de elementos vectoriales divisibles por tres se combina con la vista que cuadra esos elementos.
Los elementos de una vista se evalúan de forma diferida. Es decir, las transformaciones que se aplican a cada elemento de una vista no se evalúan hasta que se solicita el elemento. Por ejemplo, si ejecuta el código siguiente en un depurador y coloca un punto de interrupción en las líneas auto divisible_by_three = ...
y auto square = ...
, verá que se alcanza el punto de interrupción lambda divisible_by_three
, ya que cada elemento de input
se prueba para la divisibilidad en tres. El punto de interrupción lambda square
se alcanzará a medida que los elementos divisibles por tres están cuadrados.
// 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;
}
Para obtener más información sobre las vistas, vea Ver <ranges>
clases.
Adaptadores de intervalo
Los adaptadores de rango toman un rango y generan una vista. Los adaptadores de intervalo producen vistas evaluadas de forma diferida. Es decir, no incurre en el costo de transformar todos los elementos del intervalo para generar la vista. Solo se paga el costo de procesar un elemento en la vista cuando se accede a ese elemento.
En el ejemplo anterior, el filter
adaptador de rango crea una vista denominada input
que contiene los elementos divisibles por tres. El transform
adaptador de rango toma la vista de los elementos divisibles por tres y crea una vista de esos elementos cuadrados.
Los adaptadores de gama se pueden encadenar (compuestos), que es el corazón de la potencia y la flexibilidad de los rangos. La composición de adaptadores de rango le permite superar el problema de que los algoritmos STL anteriores no se pueden componer fácilmente.
Para obtener más información sobre la creación de vistas, vea Adaptadores de rango.
Algoritmos de intervalo
Algunos algoritmos de intervalo toman un argumento de intervalo. Un ejemplo es std::ranges::sort(myVector);
.
Los algoritmos de intervalo son casi idénticos a los algoritmos de pares iteradores correspondientes en el std
espacio de nombres. La diferencia es que tienen restricciones impuestas por concepto y aceptan argumentos de intervalo o más pares de argumentos iterator-sentinel. Pueden trabajar directamente en un contenedor y se pueden encadenar fácilmente.
funciones<ranges>
Las siguientes funciones se usan para crear iteradores y sentinels para rangos y para obtener el tamaño de un intervalo.
Función | Descripción |
---|---|
begin C++20 |
Obtiene un iterador al primer elemento del rango. |
cbegin C++20 |
Obtiene un iterador const al primer elemento del rango. |
cend C++20 |
Obtiene el centinela que se encuentra al final del rango calificado por const . |
cdata C++20 |
Obtiene un puntero const al primer elemento del rango contiguo. |
crbegin C++20 |
Obtiene un iterador inverso const al principio del rango. |
crend C++20 |
Obtiene el centinela que se encuentra al final de lo que crbegin() devuelve. |
data C++20 |
Obtiene un puntero al primer elemento del rango contiguo. |
empty C++20 |
Determina si el rango está vacío. |
end C++20 |
Obtiene el centinela que se encuentra al final del rango. |
rbegin C++20 |
Obtiene un iterador inverso al principio del rango. |
rend C++20 |
Obtiene un iterador inverso al centinela que se encuentra al final del rango. |
size C++20 |
Obtiene el tamaño del rango como un valor sin signo. |
ssize C++20 |
Obtiene el tamaño del rango como un valor con signo. |
Para más información, consulte funciones <ranges>
.
Conceptos de rango
La forma en que recorre en iteración los elementos de un intervalo depende de su tipo de iterador subyacente. Los intervalos usan conceptos de C++ que especifican qué iterador admiten.
En C++20, para decir que el concepto X refina el concepto Y significa que todo lo que satisface el concepto Y también satisface el concepto X. Por ejemplo: coche, autobús y camión refinan todo el vehículo.
Algunos conceptos de rango reflejan la jerarquía de categorías de iterador. En la tabla siguiente se enumeran los conceptos de intervalo, junto con los tipos de contenedores a los que se pueden aplicar.
Concepto de intervalo | Descripción | Contenedores admitidos |
---|---|---|
std::ranges::output_range |
Puede iterar hacia delante. | |
std::ranges::input_range |
Puede iterar de principio a fin al menos una vez. | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset basic_istream_view |
std::ranges::forward_range |
Puede iterar de principio a fin más de una vez. | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset |
std::ranges::bidirectional_range |
Puede iterar hacia delante y hacia atrás más de una vez. | std::list std::map std::multimap std::multiset std::set |
std::ranges::random_access_range |
Puede acceder a un elemento arbitrario (en tiempo constante) mediante el [] operador . |
std::deque |
std::ranges::contiguous_range |
Los elementos se almacenan en memoria consecutivamente. | std::array std::string std::vector |
Consulte <ranges>
los conceptos para obtener más información sobre estos conceptos.
Plantillas de alias <ranges>
Las siguientes plantillas de alias determinan los tipos de iteradores y sentinels de un intervalo:
Plantilla de alias | Descripción |
---|---|
borrowed_iterator_t C++20 |
Determine si un iterador devuelto para un range hace referencia a un intervalo cuya duración ha finalizado. |
borrowed_subrange_t C++20 |
Determine si un iterador devuelto para un subrange hace referencia a un subrango cuya duración ha finalizado. |
dangling C++20 |
Indica que el iterador devuelto de una range /subrange vida útil de la duración a la range /subrange que hace referencia. |
iterator_t C++20 |
Devuelve el tipo de iterador del tipo de intervalo especificado. |
range_difference_t C++20 |
Devuelve el tipo de diferencia del tipo de iterador del intervalo especificado. |
range_reference_t C++20 |
Devuelve el tipo de referencia del tipo de iterador del intervalo especificado. |
range_rvalue_reference_t C++20 |
Devuelve el tipo de referencia rvalue para el tipo de iterador del intervalo especificado. En otras palabras, el tipo de referencia rvalue de los elementos del intervalo. |
range_size_t C++20 |
Devuelve el tipo usado para notificar el tamaño del intervalo especificado. |
range_value_t C++20 |
Devuelve el tipo de valor del tipo de iterador del intervalo especificado. O en otras palabras, el tipo de los elementos del intervalo. |
sentinel_t C++20 |
Devuelve el tipo centinela del intervalo especificado. |
Para obtener más información sobre estas plantillas de alias, consulte <ranges>
plantillas de alias.
Consulte también
Funciones <ranges>
<ranges>
Conceptos
Adaptadores de rango
Referencia de archivos de encabezado