Compartir a través de


Conceptos de iterador

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 la 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 la versión preliminar 4 o posterior de Visual Studio 2022, la dividable<char*> versión preliminar 4 o posterior de Visual Studio 2022 apuntará directamente al requisito (a / b) de expresión que produjo un error.

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

Hay seis categorías de iteradores. Están directamente relacionados con las categorías de rangos que aparecen en Conceptos de rango.

Los siguientes conceptos de iterador se enumeran en orden de aumentar la capacidad. input_or_output_iterator se encuentra en el extremo inferior de la jerarquía de funcionalidades y contiguous_iterator se encuentra en el extremo superior. Los iteradores superiores en la jerarquía se pueden usar generalmente en lugar de los que son inferiores, pero no viceversa. Por ejemplo, se puede usar un random_access_iterator iterador en lugar de , forward_iteratorpero no en el otro lugar. Una excepción es input_iterator, que no se puede usar en lugar de output_iterator porque no se puede escribir.

Diagrama de la jerarquía de iteradores. input_or_output_iterator es la base. input_iterator y output_iterator se muestran como input_or_output_iterator de refinamiento. forward_iterator es siguiente y refina tanto input_iterator como output_iterator. bidirectional_iterator refina forward_iterator. random_access_iterator refina bidirectional_iterator. Por último, contiguous_iterator refina random_access_iterator

En la tabla siguiente, "Multi-pass" hace referencia a si el iterador puede volver a visitar el mismo elemento más de una vez. Por ejemplo, vector::iterator es un iterador de varios pasos porque puede realizar una copia del iterador, leer los elementos de la colección y, a continuación, restaurar el iterador al valor de la copia y volver a revisar los mismos elementos de nuevo. Si un iterador es de un solo paso, solo puede visitar los elementos de la colección una vez.

En la tabla siguiente, "Tipos de ejemplo" hace referencia a colecciones o iteradores que satisfacen el concepto.

Concepto de iterador Descripción Dirección Lectura/escritura Multi-pass Tipos de ejemplo
input_or_output_iteratorC++20 La base de la taxonomía del concepto de iterador. Adelante Lectura/escritura no istream_iterator, ostream_iterator
output_iteratorC++20 Especifica un iterador en el que se puede escribir. Adelante Escribir no ostream, inserter
input_iteratorC++20 Especifica un iterador que se puede leer de una vez. Adelante Leer no istream, istreambuf_iterator
forward_iteratorC++20 Especifica un iterador que puede leer (y posiblemente escribir) varias veces. Adelante Lectura/escritura vector, list
bidirectional_iteratorC++20 Especifica un iterador que se puede leer y escribir tanto hacia delante como hacia atrás. Adelante o atrás Lectura/escritura list, set, multiset, map y multimap.
random_access_iteratorC++20 Especifica un iterador que se puede leer y escribir por índice. Adelante o atrás Lectura/escritura vector, , array, deque
contiguous_iteratorC++20 Especifica un iterador 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. Adelante o atrás Lectura/escritura array, vector string.

Otros conceptos de iterador incluyen:

Concepto de iterador Descripción
sentinel_forC++20 Especifica que un tipo es un sentinel para un tipo de iterador.
sized_sentinel_forC++20 Especifica que un iterador y su centinela se pueden restar (mediante -) para encontrar su diferencia en el tiempo constante.

bidirectional_iterator

Un bidirectional_iterator admite la lectura y escritura hacia delante y hacia atrás.

template<class I>
concept bidirectional_iterator =
    forward_iterator<I> &&
    derived_from<ITER_CONCEPT(I), bidirectional_iterator_tag> &&
    requires(I i) {
        {--i} -> same_as<I&>;
        {i--} -> same_as<I>;
};

Parámetros

I
Iterador que se va a probar para ver si es .bidirectional_iterator

Comentarios

tiene bidirectional_iterator las funcionalidades de , forward_iteratorpero también puede iterar hacia atrás.

Algunos ejemplos de contenedores que se pueden usar con bidirectional_iterator son set, multiset, map, multimap, vectory list.

Ejemplo: bidirectional_iterator

En el ejemplo siguiente se usa el bidirectional_iterator concepto para mostrar que vector<int> tiene un bidirectional_iterator:

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    std::cout << std::boolalpha << std::bidirectional_iterator<std::vector<int>::iterator> << '\n'; // outputs "true"

    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::contiguous_iterator<decltype(v)::iterator>; // outputs true
}

contiguous_iterator

Especifica un iterador 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.

template<class I>
    concept contiguous_iterator =
        random_access_iterator<I> &&
        derived_from<ITER_CONCEPT(I), contiguous_iterator_tag> &&
        is_lvalue_reference_v<iter_reference_t<I>> &&
        same_as<iter_value_t<I>, remove_cvref_t<iter_reference_t<I>>> &&
        requires(const I& i) {
            { to_address(i) } -> same_as<add_pointer_t<iter_reference_t<I>>>;
        };

Parámetros

I
Tipo que se va a probar para ver si es .contiguous_iterator

Comentarios

Se contiguous_iterator puede acceder a un objeto aritmético de puntero porque los elementos se colocan secuencialmente en la memoria y tienen el mismo tamaño. Algunos ejemplos de contiguous_iterator son array, vectory string.

Ejemplo: contiguous_iterator

En el ejemplo siguiente se usa el contiguous_iterator concepto para mostrar que un vector<int> objeto tiene un contiguous_iterator:

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    // Show that vector<int> has a contiguous_iterator
    std::cout << std::boolalpha << std::contiguous_iterator<std::vector<int>::iterator> << '\n'; // outputs "true"
    
    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::contiguous_iterator<decltype(v)::iterator>; // outputs true
}

forward_iterator

Tiene las funciones de y input_iterator .output_iterator Admite la iteración en una colección varias veces.

template<class I>
    concept forward_iterator =
        input_iterator<I> &&
        derived_from<ITER_CONCEPT(I), forward_iterator_tag> &&
        incrementable<I> &&
        sentinel_for<I, I>;

Parámetros

I
Iterador que se va a probar para ver si es .forward_iterator

Comentarios

Un forward_iterator solo puede avanzar.

Algunos ejemplos de contenedores que se pueden usar con forward_iterator son vector, list, unordered_set, unordered_multiset, unordered_mapy unordered_multimap.

Ejemplo: forward_iterator

En el ejemplo siguiente se usa el forward_iterator concepto para mostrar que un vector<int> objeto tiene un forward_iterator:

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    // Show that vector has a forward_iterator
    std::cout << std::boolalpha << std::forward_iterator<std::vector<int>::iterator> << '\n'; // outputs "true"

    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::forward_iterator<decltype(v)::iterator>; // outputs true
}

input_iterator

Es input_iterator un iterador que se puede leer al menos una vez.

template<class I>
concept input_iterator =
    input_or_output_iterator<I> &&
    indirectly_readable<I> &&
    requires { typename ITER_CONCEPT(I); } &&
    derived_from<ITER_CONCEPT(I), input_iterator_tag>;

Parámetros

I
Tipo que se va a probar para ver si es .input_iterator

Comentarios

Llamar a begin() en más de una input_iterator vez da como resultado un comportamiento indefinido. Tipo que solo los modelos input_iterator no son de paso múltiple. Considere la posibilidad de leer desde la entrada estándar (cin) por ejemplo. En este caso, solo puede leer el elemento actual una vez y no puede volver a leer los caracteres que ya ha leído. Un input_iterator solo lee hacia delante, no hacia atrás.

Ejemplo: input_iterator

En el ejemplo siguiente se usa el input_iterator concepto para mostrar que un istream_iterator objeto tiene un input_iterator:

// requires /std:c++20 or later
#include <iostream>

int main()
{
    // Show that a istream_iterator has an input_iterator
    std::cout << std::boolalpha << std::input_iterator<std::istream_iterator<int>>; // outputs true
}

input_or_output_iterator

Es input_or_output_iterator la base de la taxonomía del concepto de iterador. Admite la desreferenciación e incremento de un iterador. Cada iterador modela input_or_output_iterator.

template<class I>
concept input_or_output_iterator =
    requires(I i) {
        { *i } -> can-reference;
    } &&
    weakly_incrementable<I>;

Parámetros

I
Tipo que se va a probar para ver si es .input_or_output_iterator

Comentarios

El concepto can-reference significa que el tipo I es una referencia, un puntero o un tipo que se puede convertir implícitamente en una referencia.

Ejemplo: input_or_output_iterator

En el ejemplo siguiente se usa el input_or_output_iterator concepto para mostrar que vector<int> tiene un input_or_output_iterator:

// requires /std:c++20 or later
#include <iostream>

int main()
{
    // Show that a vector has an input_or_output_iterator
    std::cout << std::boolalpha << std::input_or_output_iterator<std::vector<int>::iterator> << '\n'; // outputs true

    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::input_or_output_iterator<decltype(v)::iterator>; // outputs true
}

output_iterator

es output_iterator un iterador en el que se puede escribir.

template<class I, class T>
concept output_iterator =
    input_or_output_iterator<I> &&
    indirectly_writable<I, T> &&
    requires(I i, T&& t) {
        *i++ = std::forward<T>(t);
    };

Parámetros

I
Tipo que se va a probar para ver si es .output_iterator

T
Tipo de los valores que se van a escribir.

Comentarios

Es output_iterator un paso único. Es decir, solo puede escribir en el mismo elemento una vez.

Ejemplo: output_iterator

En el ejemplo siguiente se usa el output_iterator concepto para mostrar que vector<int> tiene un output_iterator:

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    // Show that vector<int> has an output_iterator
    std::cout << std::boolalpha << std::output_iterator<std::vector<int>::iterator, int> << "\n"; // outputs "true"

    // another way to test
    std::vector<int> v = {0,1,2,3,4,5};
    std::cout << std::boolalpha << std::output_iterator<decltype(v)::iterator, int>; // outputs true
}

random_access_iterator

Un random_access_iterator puede leer o escribir por índice.

template<class I>
concept random_access_iterator =
    bidirectional_iterator<I> &&
    derived_from<ITER_CONCEPT(I), random_access_iterator_tag> &&
    totally_ordered<I> &&
    sized_sentinel_for<I, I> &&
    requires(I i, const I j, const iter_difference_t<I> n) {
        { i += n } -> same_as<I&>;
        { j + n } -> same_as<I>;
        { n + j } -> same_as<I>;
        { i -= n } -> same_as<I&>;
        { j - n } -> same_as<I>;
        { j[n] } -> same_as<iter_reference_t<I>>;
    };

Parámetros

I
Tipo que se va a probar para ver si es .random_access_iterator

Comentarios

tiene random_access_iterator las funciones de , input_iteratoroutput_iterator, forward_iteratory bidirectional_iterator.

Algunos ejemplos de random_access_iterator son vector, arrayy deque.

Ejemplo: random_access_iterator

En el ejemplo siguiente se muestra que un vector<int> objeto tiene :random_access_iterator

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    // Show that vector<int> has a random_access_iterator
    std::cout << std::boolalpha << std::random_access_iterator<std::vector<int>::iterator> << '\n'; // outputs "true"

    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::random_access_iterator<decltype(v)::iterator>; // outputs true
}    

sentinel_for

Especifica que un tipo es un sentinel para un iterador.

template<class S, class I>
concept sentinel_for =
    semiregular<S> &&
    input_or_output_iterator<I> &&
    weakly-equality-comparable-with <S, I>;

Parámetros

I
Tipo de iterador.

S
Tipo que se va a probar para ver si es un sentinel para I.

Comentarios

Un sentinel es un tipo que se puede comparar con un iterador para determinar si el iterador ha llegado al final. Este concepto determina si un tipo es un sentinel para uno de los input_or_output_iterator tipos, que incluye input_iterator, , forward_iteratoroutput_iterator, bidirectional_iterator, y random_access_iteratorcontiguous_iterator.

Ejemplo: sentinel_for

En el ejemplo siguiente se usa el sentinel_for concepto para mostrar que vector<int>::iterator es un sentinel para vector<int>:

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v = {0, 1, 2};
    std::vector<int>::iterator i = v.begin();
    // show that vector<int>::iterator is a sentinel for vector<int>
    std::cout << std::boolalpha << std::sentinel_for<std::vector<int>::iterator, decltype(i)>; // outputs true
}    

sized_sentinel_for

Pruebe que un iterador y su centinela se pueden restar utilizando - para encontrar la diferencia, en tiempo constante.

template<class S, class I>
concept sized_sentinel_for =
    sentinel_for<S, I> &&
    !disable_sized_sentinel_for<remove_cv_t<S>, remove_cv_t<I>> &&
    requires(const I& i, const S& s) {
        {s - i} -> same_as<iter_difference_t<I>>;
        {i - s} -> same_as<iter_difference_t<I>>;
    };

Parámetros

I
Tipo de iterador.

S
Tipo centinela que se va a probar.

Comentarios

Ejemplo: sized_sentinel_for

En el ejemplo siguiente se usa el sized_sentinel_for concepto para comprobar que el sentinel de un vector<int> elemento se puede restar del iterador de vectores en tiempo constante:

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v = { 1, 2, 3 };
    std::vector<int>::iterator i = v.begin();
    std::vector<int>::iterator end = v.end();
    // use the sized_sentinel_for concept to verify that i can be subtracted from end in constant time
    std::cout << std::boolalpha << std::sized_sentinel_for<decltype(end), decltype(i)> << "\n"; // outputs true
    std::cout << end - i; // outputs 3
}    

Consulte también

Conceptos de rango
Adaptadores de rango
Ver clases