Compartir vía


<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_ofcopy_iffind_ifall_ofcopy_nnone_offind, for_eachcount_iffor_each_nfind_if_notcount, equaly .mismatchcopy<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
beginC++20 Obtiene un iterador al primer elemento del rango.
cbeginC++20 Obtiene un iterador const al primer elemento del rango.
cendC++20 Obtiene el centinela que se encuentra al final del rango calificado por const.
cdataC++20 Obtiene un puntero const al primer elemento del rango contiguo.
crbeginC++20 Obtiene un iterador inverso const al principio del rango.
crendC++20 Obtiene el centinela que se encuentra al final de lo que crbegin() devuelve.
dataC++20 Obtiene un puntero al primer elemento del rango contiguo.
emptyC++20 Determina si el rango está vacío.
endC++20 Obtiene el centinela que se encuentra al final del rango.
rbeginC++20 Obtiene un iterador inverso al principio del rango.
rendC++20 Obtiene un iterador inverso al centinela que se encuentra al final del rango.
sizeC++20 Obtiene el tamaño del rango como un valor sin signo.
ssizeC++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_tC++20 Determine si un iterador devuelto para un range hace referencia a un intervalo cuya duración ha finalizado.
borrowed_subrange_tC++20 Determine si un iterador devuelto para un subrange hace referencia a un subrango cuya duración ha finalizado.
danglingC++20 Indica que el iterador devuelto de una range/subrange vida útil de la duración a la range/subrange que hace referencia.
iterator_tC++20 Devuelve el tipo de iterador del tipo de intervalo especificado.
range_difference_tC++20 Devuelve el tipo de diferencia del tipo de iterador del intervalo especificado.
range_reference_tC++20 Devuelve el tipo de referencia del tipo de iterador del intervalo especificado.
range_rvalue_reference_tC++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_tC++20 Devuelve el tipo usado para notificar el tamaño del intervalo especificado.
range_value_tC++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_tC++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