Partager via


Concepts d’itérateur

Les concepts sont une fonctionnalité de langage C++20 qui limite les paramètres de modèle au moment de la compilation. Ils empêchent l’instanciation incorrecte du modèle, spécifient les exigences d’argument de modèle dans un formulaire lisible et fournissent des erreurs de compilateur liées au modèle plus succinctes.

Prenons l’exemple suivant, qui définit un concept pour empêcher l’instanciation du modèle avec un type qui ne prend pas en charge la division :

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

Lorsque vous passez le commutateur /diagnostics:caret du compilateur à Visual Studio 2022 version 17.4 preview 4 ou ultérieure, l’erreur évaluée dividable<char*> à false pointe directement vers la condition d’expression (a / b) qui a échoué.

Les concepts d’itérateur sont définis dans l’espace std de noms et sont déclarés dans le fichier d’en-tête <iterator> . Ils sont utilisés dans les déclarations des adaptateurs de plage, des vues, et ainsi de suite.

Il existe six catégories d’itérateurs. Ils sont directement liés aux catégories de plages répertoriées sous concepts de plage.

Les concepts d’itérateur suivants sont répertoriés dans l’ordre d’augmentation des capacités. input_or_output_iterator est à l’extrémité inférieure de la hiérarchie des capacités et contiguous_iterator se trouve à l’extrémité supérieure. Les itérateurs supérieurs dans la hiérarchie peuvent généralement être utilisés à la place de ceux qui sont inférieurs, mais pas inversement. Par exemple, un random_access_iterator itérateur peut être utilisé à la place d’un forward_iterator, mais pas de l’autre façon. Une exception est input_iterator, qui ne peut pas être utilisée à la place du fait qu’elle output_iterator ne peut pas écrire.

Diagramme de la hiérarchie d’itérateur. input_or_output_iterator est la base. input_iterator et output_iterator sont montrés comme des input_or_output_iterator d’affinement. forward_iterator est ensuite affinée à la fois input_iterator et output_iterator. bidirectional_iterator affine forward_iterator. random_access_iterator affine bidirectional_iterator. Enfin, contiguous_iterator affine random_access_iterator

Dans le tableau suivant, « Multi-pass » désigne si l’itérateur peut revisiter le même élément plusieurs fois. Par exemple, vector::iterator il s’agit d’un itérateur à plusieurs passes, car vous pouvez effectuer une copie de l’itérateur, lire les éléments de la collection, puis restaurer l’itérateur sur la valeur de la copie, puis revoir les mêmes éléments. Si un itérateur est une seule passe, vous ne pouvez visiter les éléments de la collection qu’une seule fois.

Dans le tableau suivant, « Exemples de types » fait référence à des collections/itérateurs qui répondent au concept.

Concept d’itérateur Description Sens Lecture/écriture Multi-passe Exemples de types
input_or_output_iteratorC++20 Base de la taxonomie du concept d’itérateur. Transférer Lecture/écriture non istream_iterator, ostream_iterator
output_iteratorC++20 Spécifie un itérateur dans lequel vous pouvez écrire. Transférer Écrire non ostream, inserter
input_iteratorC++20 Spécifie un itérateur à partir duquel vous pouvez lire une seule fois. Transférer Lire non istream, istreambuf_iterator
forward_iteratorC++20 Spécifie un itérateur qui peut lire (et éventuellement écrire) plusieurs fois. Transférer Lecture/écriture Oui vector, list
bidirectional_iteratorC++20 Spécifie un itérateur que vous pouvez lire et écrire à la fois vers l’avant et vers l’arrière. En avant ou en arrière Lecture/écriture Oui list, set, multiset, map et multimap.
random_access_iteratorC++20 Spécifie un itérateur que vous pouvez lire et écrire par index. En avant ou en arrière Lecture/écriture Oui vector, , arraydeque
contiguous_iteratorC++20 Spécifie un itérateur dont les éléments sont séquentiels en mémoire, sont de la même taille et sont accessibles à l’aide de l’arithmétique du pointeur. En avant ou en arrière Lecture/écriture Oui array, vector string.

Voici d’autres concepts d’itérateur :

Concept d’itérateur Description
sentinel_forC++20 Spécifie qu’un type est une sentinelle pour un type d’itérateur.
sized_sentinel_forC++20 Spécifie qu’un itérateur et son sentinelle peuvent être soustraits (à l’aide -) pour trouver leur différence dans le temps constant.

bidirectional_iterator

A bidirectional_iterator prend en charge la lecture et l’écriture vers l’avant et vers l’arrière.

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

Paramètres

I
Itérateur à tester pour voir s’il s’agit d’un bidirectional_iterator.

Notes

A bidirectional_iterator a les capacités d’un forward_iterator, mais peut également itérer vers l’arrière.

Voici quelques exemples de conteneurs qui peuvent être utilisés avec un bidirectional_iterator sont set, , mapmultiset, multimap, , vectoret list.

Exemple : bidirectional_iterator

L’exemple suivant utilise le bidirectional_iterator concept pour montrer qu’il vector<int> a 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

Spécifie un itérateur dont les éléments sont séquentiels en mémoire, sont de la même taille et sont accessibles à l’aide de l’arithmétique du pointeur.

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

Paramètres

I
Type à tester pour voir s’il s’agit d’un contiguous_iterator.

Notes

Un contiguous_iterator accès est accessible par arithmétique du pointeur, car les éléments sont disposés séquentiellement en mémoire et sont de la même taille. Quelques exemples d’un contiguous_iterator sont array, vectoret string.

Exemple : contiguous_iterator

L’exemple suivant utilise le contiguous_iterator concept pour montrer qu’un vector<int> objet a :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

Dispose des fonctionnalités d’un input_iterator et d’un output_iterator. Prend en charge l’itération sur une collection plusieurs fois.

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

Paramètres

I
Itérateur à tester pour voir s’il s’agit d’un forward_iterator.

Notes

A forward_iterator ne peut avancer que.

Voici quelques exemples de conteneurs qui peuvent être utilisés avec un forward_iterator sont vector, , unordered_setlist, unordered_multiset, , unordered_mapet unordered_multimap.

Exemple : forward_iterator

L’exemple suivant utilise le forward_iterator concept pour montrer qu’un vector<int> objet a :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

Il input_iterator s’agit d’un itérateur que vous pouvez lire au moins une fois.

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

Paramètres

I
Type à tester pour voir s’il s’agit d’un input_iterator.

Notes

L’appel begin() à plusieurs input_iterator reprises entraîne un comportement non défini. Type que seuls les modèles input_iterator ne sont pas multi-passe. Envisagez de lire à partir d’une entrée standard (cin) par exemple. Dans ce cas, vous ne pouvez lire l’élément actuel qu’une seule fois et vous ne pouvez pas lire les caractères que vous avez déjà lus. Une input_iterator seule lecture vers l’avant, pas vers l’arrière.

Exemple : input_iterator

L’exemple suivant utilise le input_iterator concept pour montrer qu’un istream_iterator élément a 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

Il input_or_output_iterator s’agit de la base de la taxonomie du concept d’itérateur. Il prend en charge le déreferencing et l’incrémentation d’un itérateur. Tous les modèles input_or_output_iteratord’itérateur .

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

Paramètres

I
Type à tester pour voir s’il s’agit d’un input_or_output_iterator.

Notes

Le concept can-reference signifie que le type I est une référence, un pointeur ou un type qui peut être implicitement converti en référence.

Exemple : input_or_output_iterator

L’exemple suivant utilise le input_or_output_iterator concept pour montrer qu’il vector<int> a 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

Il output_iterator s’agit d’un itérateur auquel vous pouvez écrire.

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

Paramètres

I
Type à tester pour voir s’il s’agit d’un output_iterator.

T
Type des valeurs à écrire.

Notes

Il s’agit d’une output_iterator seule passe. Autrement dit, il ne peut écrire qu’une seule fois dans le même élément.

Exemple : output_iterator

L’exemple suivant utilise le output_iterator concept pour montrer qu’il vector<int> a 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 peut lire ou écrire par index.

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

Paramètres

I
Type à tester pour voir s’il s’agit d’un random_access_iterator.

Notes

A random_access_iterator a les capacités d’un input_iterator, , output_iteratorforward_iterator, et bidirectional_iterator.

Quelques exemples d’un random_access_iterator sont vector, arrayet deque.

Exemple : random_access_iterator

L’exemple suivant montre qu’un vector<int> a :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

Spécifie qu’un type est une sentinelle pour un itérateur.

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

Paramètres

I
Type d'itérateur.

S
Type à tester pour voir s’il s’agit d’une sentinelle pour I.

Notes

Une sentinelle est un type qui peut être comparé à un itérateur pour déterminer si l’itérateur a atteint la fin. Ce concept détermine si un type est une sentinelle pour l’un des input_or_output_iterator types, qui inclut input_iterator, , output_iteratorforward_iterator, bidirectional_iterator, , random_access_iteratoret contiguous_iterator.

Exemple : sentinel_for

L’exemple suivant utilise le sentinel_for concept pour montrer qu’il vector<int>::iterator s’agit d’une sentinelle pour 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

Testez qu’un itérateur et son sentinelle peuvent être soustraits à l’aide - de la différence, en temps constant.

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

Paramètres

I
Type d'itérateur.

S
Type sentinelle à tester.

Notes

Exemple : sized_sentinel_for

L’exemple suivant utilise le sized_sentinel_for concept pour vérifier que la sentinelle pour un vector<int> peut être soustraite à partir de l’itérateur de vecteurs dans le temps constant :

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

Voir aussi

Concepts de plage
Adaptateurs de plage
Afficher les classes