Concetti relativi all'iteratore

I concetti sono una funzionalità del linguaggio C++20 che vincola i parametri del modello in fase di compilazione. Consentono di evitare la creazione di istanze di modelli non corretti, specificare i requisiti degli argomenti del modello in un modulo leggibile e fornire altri errori del compilatore correlati al modello.

Si consideri l'esempio seguente, che definisce un concetto per impedire la creazione di istanze del modello con un tipo che non supporta la divisione:

// 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 si passa l'opzione /diagnostics:caret del compilatore a Visual Studio 2022 versione 17.4 preview 4 o successiva, l'errore valutato dividable<char*> su false punterà direttamente al requisito (a / b) dell'espressione che non è riuscito.

I concetti dell'iteratore vengono definiti nello spazio dei std nomi e vengono dichiarati nel <iterator> file di intestazione. Vengono usate nelle dichiarazioni di adattatori di intervallo, visualizzazioni e così via.

Esistono sei categorie di iteratori. Sono direttamente correlati alle categorie di intervalli elencati in Concetti relativi all'intervallo.

I concetti dell'iteratore seguenti sono elencati in ordine di aumento delle funzionalità. input_or_output_iterator è alla fine bassa della gerarchia delle funzionalità ed contiguous_iterator è all'estremità alta. Gli iteratori più alti nella gerarchia possono essere in genere usati al posto di quelli inferiori, ma non viceversa. Ad esempio, un random_access_iterator iteratore può essere usato al posto di un forward_iteratoroggetto , ma non in altro modo. Un'eccezione è input_iterator, che non può essere usata al posto di output_iterator perché non può scrivere.

Diagramma della gerarchia dell'iteratore. input_or_output_iterator è la base. input_iterator e output_iterator vengono visualizzati come input_or_output_iterator di affinamento. forward_iterator è successivo e affina sia input_iterator che output_iterator. bidirectional_iterator affina forward_iterator. random_access_iterator affina bidirectional_iterator. Infine, contiguous_iterator affina random_access_iterator

Nella tabella seguente , "Multi-pass" indica se l'iteratore può rivedere lo stesso elemento più di una volta. Ad esempio, vector::iterator è un iteratore a più passaggi perché è possibile creare una copia dell'iteratore, leggere gli elementi nella raccolta e quindi ripristinare l'iteratore sul valore nella copia e rivedere nuovamente gli stessi elementi. Se un iteratore è a passaggio singolo, è possibile visitare solo gli elementi nella raccolta una sola volta.

Nella tabella seguente "Tipi di esempio" si riferisce a raccolte/iteratori che soddisfano il concetto.

Concetto di iteratore Descrizione Direzione Lettura/scrittura Multi-pass Tipi di esempio
input_or_output_iteratorC++20 Base della tassonomia del concetto di iteratore. Inoltra Lettura/scrittura no istream_iterator, ostream_iterator
output_iteratorC++20 Specifica un iteratore in cui è possibile scrivere. Inoltra Scrittura no ostream, inserter
input_iteratorC++20 Specifica un iteratore da cui è possibile leggere una sola volta. Inoltra Lettura no istream, istreambuf_iterator
forward_iteratorC++20 Specifica un iteratore in grado di leggere (ed eventualmente scrivere) più volte. Inoltra Lettura/scrittura yes vector, list
bidirectional_iteratorC++20 Specifica un iteratore che è possibile leggere e scrivere sia avanti che indietro. Avanti o indietro Lettura/scrittura yes list, set, multiset, map e multimap.
random_access_iteratorC++20 Specifica un iteratore che è possibile leggere e scrivere in base all'indice. Avanti o indietro Lettura/scrittura yes vector, array, deque
contiguous_iteratorC++20 Specifica un iteratore i cui elementi sono sequenziali in memoria, sono le stesse dimensioni e possono essere accessibili usando l'aritmetica del puntatore. Avanti o indietro Lettura/scrittura yes array, vector string.

Altri concetti dell'iteratore includono:

Concetto di iteratore Descrizione
sentinel_forC++20 Specifica che un tipo è un sentinel per un tipo di iteratore.
sized_sentinel_forC++20 Specifica che un iteratore e il relativo sentinella possono essere sottratti (usando -) per trovare la differenza nel tempo costante.

bidirectional_iterator

Un bidirectional_iterator oggetto supporta la lettura e la scrittura avanti e indietro.

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

Parametri

I
Iteratore da testare per verificare se si tratta di un oggetto bidirectional_iterator.

Osservazioni:

Un bidirectional_iterator oggetto ha le funzionalità di un forward_iteratoroggetto , ma può anche eseguire un'iterazione all'indietro.

Alcuni esempi di contenitori che possono essere usati con un bidirectional_iterator sono set, , multimapvectormultisetmape .list

Esempio: bidirectional_iterator

Nell'esempio seguente viene usato il bidirectional_iterator concetto per indicare che vector<int> ha un oggetto 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

Specifica un iteratore i cui elementi sono sequenziali in memoria, sono le stesse dimensioni e possono essere accessibili usando l'aritmetica del puntatore.

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

Parametri

I
Tipo da testare per verificare se si tratta di un oggetto contiguous_iterator.

Osservazioni:

È possibile accedere a un contiguous_iterator oggetto tramite aritmetica del puntatore perché gli elementi sono disposti in modo sequenziale in memoria e hanno le stesse dimensioni. Alcuni esempi di sono contiguous_iterator array, vectore string.

Esempio: contiguous_iterator

Nell'esempio seguente viene usato il contiguous_iterator concetto per indicare che un vector<int> oggetto ha un oggetto 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

Dispone delle funzionalità di un oggetto input_iterator e di un oggetto output_iterator. Supporta l'iterazione su una raccolta più volte.

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

Parametri

I
Iteratore da testare per verificare se si tratta di un oggetto forward_iterator.

Osservazioni:

Un forward_iterator oggetto può spostarsi solo in avanti.

Alcuni esempi di contenitori che possono essere usati con un forward_iterator sono vector, , unordered_multisetunordered_maplistunordered_sete .unordered_multimap

Esempio: forward_iterator

Nell'esempio seguente viene usato il forward_iterator concetto per indicare che un vector<int> oggetto ha un oggetto 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

È input_iterator un iteratore che è possibile leggere almeno una volta.

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>;

Parametri

I
Tipo da testare per verificare se si tratta di un oggetto input_iterator.

Osservazioni:

La chiamata begin() a più input_iterator volte comporta un comportamento non definito. Un tipo che solo i modelli input_iterator non sono multi-pass. Si consideri ad esempio la lettura dall'input standard (cin). In questo caso, è possibile leggere l'elemento corrente una sola volta e non è possibile leggere nuovamente i caratteri già letti. Un input_iterator solo legge in avanti, non all'indietro.

Esempio: input_iterator

Nell'esempio seguente viene usato il input_iterator concetto per indicare che un oggetto istream_iterator ha un oggetto 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

Un input_or_output_iterator è la base della tassonomia del concetto di iteratore. Supporta la dereferenziazione e l'incremento di un iteratore. Ogni iteratore modella input_or_output_iterator.

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

Parametri

I
Tipo da testare per verificare se si tratta di un oggetto input_or_output_iterator.

Osservazioni:

Il concetto can-reference significa che il tipo I è un riferimento, un puntatore o un tipo che può essere convertito in modo implicito in un riferimento.

Esempio: input_or_output_iterator

Nell'esempio seguente viene usato il input_or_output_iterator concetto per indicare che vector<int> ha un oggetto 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

È output_iterator un iteratore in cui è possibile scrivere.

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);
    };

Parametri

I
Tipo da testare per verificare se si tratta di un oggetto output_iterator.

T
Tipo dei valori da scrivere.

Osservazioni:

È output_iterator un singolo passaggio. Ovvero, può scrivere nello stesso elemento una sola volta.

Esempio: output_iterator

Nell'esempio seguente viene usato il output_iterator concetto per indicare che vector<int> ha un oggetto 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 oggetto può leggere o scrivere in base all'indice.

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

Parametri

I
Tipo da testare per verificare se si tratta di un oggetto random_access_iterator.

Osservazioni:

Un random_access_iterator oggetto ha le funzionalità di un input_iteratoroggetto , output_iterator, forward_iteratore bidirectional_iterator.

Alcuni esempi di sono random_access_iterator vector, arraye deque.

Esempio: random_access_iterator

L'esempio seguente mostra che un oggetto vector<int> ha un oggetto 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

Specifica che un tipo è un sentinel per un iteratore.

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

Parametri

I
Tipo di iteratore.

S
Tipo da testare per verificare se si tratta di un sentinel per I.

Osservazioni:

Un sentinel è un tipo che può essere confrontato con un iteratore per determinare se l'iteratore ha raggiunto la fine. Questo concetto determina se un tipo è un sentinel per uno dei input_or_output_iterator tipi, che include input_iterator, bidirectional_iteratorrandom_access_iteratoroutput_iteratorforward_iteratore .contiguous_iterator

Esempio: sentinel_for

Nell'esempio seguente viene usato il sentinel_for concetto per indicare che vector<int>::iterator è un sentinel per 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

Verificare che un iteratore e il relativo sentinella possano essere sottratti usando - per trovare la differenza, in tempo costante.

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

Parametri

I
Tipo di iteratore.

S
Tipo di sentinel da testare.

Osservazioni:

Esempio: sized_sentinel_for

Nell'esempio seguente viene usato il sized_sentinel_for concetto per verificare che l'iteratore sentinel per un vector<int> oggetto possa essere sottratto dagli iteratori vettoriali in tempo costante:

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

Vedi anche

Concetti relativi all'intervallo
Adattatori di intervallo
Visualizzare le classi