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_iterator
pero 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.
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 y escritura | Multi-pass | Tipos de ejemplo |
---|---|---|---|---|---|
input_or_output_iterator C++20 |
La base de la taxonomía del concepto de iterador. | Adelante | Lectura y escritura | no | istream_iterator , ostream_iterator |
output_iterator C++20 |
Especifica un iterador en el que se puede escribir. | Adelante | Escribir | no | ostream , inserter |
input_iterator C++20 |
Especifica un iterador que se puede leer de una vez. | Adelante | Lectura | no | istream , istreambuf_iterator |
forward_iterator C++20 |
Especifica un iterador que puede leer (y posiblemente escribir) varias veces. | Adelante | Lectura y escritura | sí | vector , list |
bidirectional_iterator C++20 |
Especifica un iterador que se puede leer y escribir tanto hacia delante como hacia atrás. | Adelante o atrás | Lectura y escritura | sí | list , set , multiset , map y multimap . |
random_access_iterator C++20 |
Especifica un iterador que se puede leer y escribir por índice. | Adelante o atrás | Lectura y escritura | sí | vector , , array , deque |
contiguous_iterator C++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 y escritura | sí | array , vector string . |
Otros conceptos de iterador incluyen:
Concepto de iterador | Descripción |
---|---|
sentinel_for C++20 |
Especifica que un tipo es un sentinel para un tipo de iterador. |
sized_sentinel_for C++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_iterator
pero también puede iterar hacia atrás.
Algunos ejemplos de contenedores que se pueden usar con bidirectional_iterator
son set
, multiset
, map
, multimap
, vector
y 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
, vector
y 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_map
y 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_iterator
output_iterator
, forward_iterator
y bidirectional_iterator
.
Algunos ejemplos de random_access_iterator
son vector
, array
y 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_iterator
output_iterator
, bidirectional_iterator
, y random_access_iterator
contiguous_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
}