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.
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_iterator C++20 |
Base de la taxonomie du concept d’itérateur. | Transférer | Lecture/écriture | non | istream_iterator , ostream_iterator |
output_iterator C++20 |
Spécifie un itérateur dans lequel vous pouvez écrire. | Transférer | Écrire | non | ostream , inserter |
input_iterator C++20 |
Spécifie un itérateur à partir duquel vous pouvez lire une seule fois. | Transférer | Lire | non | istream , istreambuf_iterator |
forward_iterator C++20 |
Spécifie un itérateur qui peut lire (et éventuellement écrire) plusieurs fois. | Transférer | Lecture/écriture | Oui | vector , list |
bidirectional_iterator C++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_iterator C++20 |
Spécifie un itérateur que vous pouvez lire et écrire par index. | En avant ou en arrière | Lecture/écriture | Oui | vector , , array deque |
contiguous_iterator C++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_for C++20 |
Spécifie qu’un type est une sentinelle pour un type d’itérateur. |
sized_sentinel_for C++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
, , map
multiset
, multimap
, , vector
et 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
, vector
et 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_set
list
, unordered_multiset
, , unordered_map
et 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_iterator
d’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_iterator
forward_iterator
, et bidirectional_iterator
.
Quelques exemples d’un random_access_iterator
sont vector
, array
et 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_iterator
forward_iterator
, bidirectional_iterator
, , random_access_iterator
et 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
}