Partilhar via


Conceitos do iterador

Os conceitos são um recurso da linguagem C++20 que restringe os parâmetros do modelo em tempo de compilação. Eles ajudam a evitar a instanciação incorreta do modelo, especificam os requisitos de argumento do modelo em um formato legível e fornecem erros mais sucintos do compilador relacionados ao modelo.

Considere o exemplo a seguir, que define um conceito para evitar a instanciação do modelo com um tipo que não suporta divisão:

// 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 
}

Quando você passa a opção /diagnostics:caret do compilador para o Visual Studio 2022 versão 17.4 preview 4 ou posterior, o erro que o conceito dividable<char*> avaliado como false apontará diretamente para o requisito (a / b) de expressão que falhou.

Os conceitos de std iterador são definidos no namespace e são declarados <iterator> no arquivo de cabeçalho. Eles são usados nas declarações de adaptadores de intervalo, visualizações e assim por diante.

Existem seis categorias de iteradores. Eles estão diretamente relacionados às categorias de intervalos listados em Conceitos de intervalo.

Os seguintes conceitos de iterador são listados em ordem crescente de capacidade. input_or_output_iterator está na extremidade inferior da hierarquia de capacidade e contiguous_iterator está na extremidade superior. Iteradores mais altos na hierarquia geralmente podem ser usados no lugar daqueles que são inferiores, mas não vice-versa. Por exemplo, um random_access_iterator iterador pode ser usado no lugar de um forward_iterator, mas não o contrário. Uma exceção é input_iterator, que não pode ser usado no lugar de output_iterator porque não pode escrever.

Diagrama da hierarquia do iterador. input_or_output_iterator é a base. input_iterator e output_iterator são mostrados como input_or_output_iterator de refinamento. forward_iterator é o próximo e refina tanto input_iterator quanto output_iterator. bidirectional_iterator refina forward_iterator. random_access_iterator refina bidirectional_iterator. Finalmente, contiguous_iterator refina random_access_iterator

Na tabela a seguir, "Multi-pass" refere-se a se o iterador pode revisitar o mesmo elemento mais de uma vez. Por exemplo, vector::iterator é um iterador de várias passagens porque você pode fazer uma cópia do iterador, ler os elementos na coleção e, em seguida, restaurar o iterador para o valor na cópia e revisitar os mesmos elementos novamente. Se um iterador for de passagem única, você só poderá visitar os elementos da coleção uma vez.

Na tabela a seguir, "Tipos de exemplo" refere-se a coleções/iteradores que satisfazem o conceito.

Conceito de iterador Descrição Direção Leitura/gravação Multi-passe Tipos de exemplo
input_or_output_iterator C++20 A base da taxonomia do conceito iterador. Avançar Leitura/gravação Não istream_iterator, ostream_iterator
output_iterator C++20 Especifica um iterador no qual você pode gravar. Avançar Escreve Não ostream, inserter
input_iterator C++20 Especifica um iterador que você pode ler uma vez. Avançar Leitura Não istream, istreambuf_iterator
forward_iterator C++20 Especifica um iterador que pode ler (e possivelmente gravar) várias vezes. Avançar Leitura/gravação Sim vector, list
bidirectional_iterator C++20 Especifica um iterador que você pode ler e gravar para frente e para trás. Para a frente ou para trás Leitura/gravação Sim list, set, multiset, map e multimap.
random_access_iterator C++20 Especifica um iterador que você pode ler e gravar por índice. Para a frente ou para trás Leitura/gravação Sim vector, array, deque
contiguous_iterator C++20 Especifica um iterador cujos elementos são sequenciais na memória, têm o mesmo tamanho e podem ser acessados usando a aritmética de ponteiro. Para a frente ou para trás Leitura/gravação Sim array, vector, string.

Outros conceitos de iterador incluem:

Conceito de iterador Descrição
sentinel_for C++20 Especifica que um tipo é um sentinela para um tipo de iterador.
sized_sentinel_for C++20 Especifica que um iterador e seu sentinela podem ser subtraídos (usando -) para encontrar sua diferença em tempo constante.

bidirectional_iterator

A bidirectional_iterator suporta leitura e escrita para a frente e para trá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
O iterador para testar para ver se é um bidirectional_iteratorarquivo .

Observações

A bidirectional_iterator tem as capacidades de um forward_iterator, mas também pode iterar para trás.

Alguns exemplos de recipientes que podem ser usados com um bidirectional_iterator são set, multiset, map, multimap, vector, e list.

Exemplo: bidirectional_iterator

O exemplo a seguir usa o bidirectional_iterator conceito para mostrar que vector<int> tem um 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 um iterador cujos elementos são sequenciais na memória, têm o mesmo tamanho e podem ser acessados usando a aritmética de ponteiro.

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
O tipo a ser testado para ver se é um contiguous_iteratorarquivo .

Observações

A contiguous_iterator pode ser acessado por aritmética de ponteiro porque os elementos são dispostos sequencialmente na memória e são do mesmo tamanho. Alguns exemplos de um contiguous_iterator são array, vector, e string.

Exemplo: contiguous_iterator

O exemplo a seguir usa o contiguous_iterator conceito para mostrar que a vector<int> tem um 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

Tem as capacidades de um input_iterator e um output_iterator. Suporta a iteração sobre uma coleção várias vezes.

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
O iterador para testar para ver se é um forward_iteratorarquivo .

Observações

A forward_iterator só pode avançar.

Alguns exemplos de recipientes que podem ser usados com um forward_iterator são vector, list, unordered_set, unordered_multiset, unordered_map, e unordered_multimap.

Exemplo: forward_iterator

O exemplo a seguir usa o forward_iterator conceito para mostrar que a vector<int> tem um 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

Um input_iterator é um iterador que você pode ler pelo menos uma 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
O tipo a ser testado para ver se é um input_iteratorarquivo .

Observações

Chamar begin() um input_iterator mais de uma vez resulta em comportamento indefinido. Um tipo que apenas modelos input_iterator não é multi-pass. Considere a leitura da entrada padrão (cin), por exemplo. Nesse caso, você só pode ler o elemento atual uma vez e não pode reler caracteres que já leu. Um input_iterator só lê para a frente, não para trás.

Exemplo: input_iterator

O exemplo a seguir usa o input_iterator conceito para mostrar que um istream_iterator tem um 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

An input_or_output_iterator é a base da taxonomia do conceito iterador. Ele suporta a desreferenciação e incremento de um iterador. Todos os modelos input_or_output_iteratoriteradores .

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

Parâmetros

I
O tipo a ser testado para ver se é um input_or_output_iteratorarquivo .

Observações

O conceito can-reference significa que o tipo I é uma referência, um ponteiro ou um tipo que pode ser implicitamente convertido em uma referência.

Exemplo: input_or_output_iterator

O exemplo a seguir usa o input_or_output_iterator conceito para mostrar que vector<int> tem um 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

Um output_iterator é um iterador para o qual você pode escrever.

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
O tipo a ser testado para ver se é um output_iteratorarquivo .

T
O tipo de valores a serem gravados.

Observações

Um output_iterator é uma única passagem. Ou seja, ele só pode escrever no mesmo elemento uma vez.

Exemplo: output_iterator

O exemplo a seguir usa o output_iterator conceito para mostrar que vector<int> tem um 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

A random_access_iterator pode ler ou escrever 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
O tipo a ser testado para ver se é um random_access_iteratorarquivo .

Observações

A random_access_iterator tem as capacidades de um input_iterator, output_iterator, forward_iterator, e bidirectional_iterator.

Alguns exemplos de um random_access_iterator são vector, array, e deque.

Exemplo: random_access_iterator

O exemplo a seguir mostra que a vector<int> tem um 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 um tipo é um sentinela para um 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
O tipo de iterador.

S
O tipo a testar para ver se é uma sentinela para I.

Observações

Uma sentinela é um tipo que pode ser comparado a um iterador para determinar se o iterador chegou ao fim. Este conceito determina se um tipo é uma sentinela para um dos input_or_output_iterator tipos, que inclui input_iterator, output_iterator, , forward_iterator, bidirectional_iteratorrandom_access_iterator, e contiguous_iterator.

Exemplo: sentinel_for

O exemplo a seguir usa o sentinel_for conceito para mostrar que vector<int>::iterator é uma sentinela 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

Teste que um iterador e seu sentinela podem ser subtraídos usando - para encontrar a diferença, em tempo 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
O tipo de iterador.

S
O tipo sentinela a testar.

Exemplo: sized_sentinel_for

O exemplo a seguir usa o sized_sentinel_for conceito para verificar se o foro vector<int> sentinela pode ser subtraído do iterador de vetores em tempo 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
}    

Ver também

Conceitos de gama
Adaptadores de gama
Ver aulas