<ranges>
concepts
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 d’un 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 de plage sont définis dans l’espace std::ranges
de noms et déclarés dans le fichier d’en-tête <ranges>
. Ils sont utilisés dans les déclarations des adaptateurs de plage, des vues, et ainsi de suite.
Il existe six catégories de plages. Ils sont liés aux catégories d’itérateurs répertoriés dans <iterator>
les concepts. Dans l’ordre d’augmentation des capacités, les catégories sont les suivantes :
Concept de plage | Description |
---|---|
output_range input_range |
Spécifie une plage dans laquelle vous pouvez écrire. Spécifie une plage à partir de laquelle vous pouvez lire une seule fois. |
forward_range |
Spécifie une plage que vous pouvez lire (et éventuellement écrire) plusieurs fois. |
bidirectional_range |
Spécifie une plage que vous pouvez lire et écrire à la fois vers l’avant et vers l’arrière. |
random_access_range |
Spécifie une plage que vous pouvez lire et écrire par index. |
contiguous_range |
Spécifie une plage 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. |
Dans le tableau précédent, les concepts sont répertoriés dans l’ordre d’augmentation des capacités. Une plage qui répond aux exigences d’un concept répond généralement aux exigences des concepts dans les lignes qui l’précèdent. Par exemple, un a random_access_range
la capacité d’un bidirectional_range
, , forward_range
input_range
et output_range
. L’exception est input_range
, qui ne peut pas être écrite dans, donc elle n’a pas les fonctionnalités de output_range
.
Voici d’autres concepts de plage :
Concept de plage | Description |
---|---|
range C++20 |
Spécifie un type qui fournit un itérateur et une sentinelle. |
borrowed_range C++20 |
Spécifie que la durée de vie des itérateurs de la plage n’est pas liée à la durée de vie de la plage. |
common_range C++20 |
Spécifie que le type de l’itérateur de la plage et le type de la sentinelle de la plage sont identiques. |
Simple_View C++20 |
Pas un concept officiel défini dans le cadre de la bibliothèque standard, mais utilisé comme concept d’assistance sur certaines interfaces. |
sized_range C++20 |
Spécifie une plage qui peut fournir son nombre d’éléments efficacement. |
view C++20 |
Spécifie un type qui a un déplacement efficace (temps constant) de construction, d’affectation et de destruction. |
viewable_range C++20 |
Spécifie un type qui est une vue ou peut être converti en un. |
bidirectional_range
A bidirectional_range
prend en charge la lecture et l’écriture de la plage vers l’avant et vers l’arrière.
template<class T>
concept bidirectional_range =
forward_range<T> && bidirectional_iterator<iterator_t<T>>;
Paramètres
T
Type à tester pour voir s’il s’agit d’un bidirectional_range
.
Notes
Ce type de plage prend en charge bidirectional_iterator
ou supérieur.
A bidirectional_iterator
a les capacités d’un forward_iterator
, mais peut également itérer vers l’arrière.
Quelques exemples d’un bidirectional_range
sont std::set
, std::vector
et std::list
.
borrowed_range
Un modèle de type borrowed_range
si la validité des itérateurs que vous obtenez à partir de l’objet peut survivre à la durée de vie de l’objet. Autrement dit, les itérateurs d’une plage peuvent être utilisés même quand la plage n’existe plus.
template<class T>
concept borrowed_range =
range<T> &&
(is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);
Paramètres
T
Type à tester pour voir s’il s’agit d’un borrowed_range
.
Notes
La durée de vie d’une plage rvalue peut se terminer par un appel de fonction, que les modèles borrowed_range
de plage soient ou non. S’il s’agit d’un borrowed_range
, vous pouvez continuer à utiliser les itérateurs avec un comportement bien défini, quel que soit le moment de la fin de la durée de vie de la plage.
Les cas où cela n’est pas vrai sont, par exemple, pour les conteneurs comme vector
ou list
parce que lorsque la durée de vie du conteneur se termine, les itérateurs font référence aux éléments qui ont été détruits.
Vous pouvez continuer à utiliser les itérateurs pour un borrowed_range
, par exemple, pour un view
type dont iota_view<int>{0, 42}
les itérateurs sont sur un ensemble de valeurs qui ne sont pas soumises à la destruction, car elles sont générées à la demande.
common_range
Le type de l’itérateur pour un common_range
est identique au type de la sentinelle. Autrement dit, begin()
et end()
renvoyer le même type.
template<class T>
concept common_range =
ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;
Paramètres
T
Type à tester pour voir s’il s’agit d’un common_range
.
Notes
L’obtention du type std::ranges::begin()
et std::ranges::end()
est importante pour les algorithmes qui calculent la distance entre deux itérateurs et pour les algorithmes qui acceptent des plages indiquées par des paires d’itérateurs.
Les conteneurs standard (par exemple, vector
) répondent aux exigences de common_range
.
contiguous_range
Les éléments d’un contiguous_range
sont stockés séquentiellement en mémoire et sont accessibles à l’aide de l’arithmétique du pointeur. Par exemple, un tableau est un contiguous_range
.
template<class T>
concept contiguous_range =
random_access_range<T> && contiguous_iterator<iterator_t<T>> &&
requires(T& t) {{ ranges::data(t) } -> same_as<add_pointer_t<range_reference_t<T>>>;};
Paramètres
T
Type à tester pour voir s’il s’agit d’un contiguous_range
.
Notes
Un contiguous_range
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. Ce type de plage prend en charge continguous_iterator
, qui est le plus flexible de tous les itérateurs.
Quelques exemples d’un contiguous_range
sont std::array
, std::vector
et std::string
.
Exemple : contiguous_range
L’exemple suivant montre l’utilisation d’un pointeur arithmétique pour accéder à un contiguous_range
:
// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>
int main()
{
// Show that vector is a contiguous_range
std::vector<int> v = {0,1,2,3,4,5};
std::cout << std::boolalpha << std::ranges::contiguous_range<decltype(v)> << '\n'; // outputs true
// Show that pointer arithmetic can be used to access the elements of a contiguous_range
auto ptr = v.data();
ptr += 2;
std::cout << *ptr << '\n'; // outputs 2
}
true
2
forward_range
Une forward_range
prise en charge de la lecture (et éventuellement de l’écriture) de la plage plusieurs fois.
template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;
Paramètres
T
Type à tester pour voir s’il s’agit d’un forward_range
.
Notes
Ce type de plage prend en charge forward_iterator
ou supérieur. Un forward_iterator
peut itérer sur une plage plusieurs fois.
input_range
Il input_range
s’agit d’une plage qui peut être lue à partir d’une seule fois.
template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;
Paramètres
T
Type à tester pour voir s’il s’agit d’un input_range
.
Notes
Lorsqu’un type répond aux exigences de input_range
:
- La
ranges::begin()
fonction retourne uninput_iterator
. Appelerbegin()
plusieurs fois sur uninput_range
comportement non défini. - Vous pouvez déréférencer une
input_iterator
valeur répétée, ce qui génère la même valeur à chaque fois. Uninput_range
n’est pas multi-passe. L’incrémentation d’un itérateur invalide toutes les copies. - Il peut être utilisé avec
ranges::for_each
. - Il prend en charge
input_iterator
ou supérieur.
output_range
Il output_range
s’agit d’une plage dans laquelle vous pouvez écrire.
template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;
Paramètres
R
Type de la plage.
T
Type des données à écrire dans la plage.
Notes
La signification est output_iterator<iterator_t<R>, T>
que le type fournit un itérateur qui peut écrire des valeurs de type T
dans une plage de type R
. En d’autres termes, il prend en charge output_iterator
ou supérieur.
random_access_range
Un random_access_range
peut lire ou écrire une plage par index.
template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;
Paramètres
T
Type à tester pour voir s’il s’agit d’un sized_range
.
Notes
Ce type de plage prend en charge random_access_iterator
ou supérieur. A random_access_range
a les capacités d’un input_range
, , output_range
forward_range
, et bidirectional_range
. A random_access_range
est triable.
Quelques exemples d’un random_access_range
sont std::vector
, std::array
et std::deque
.
range
Définit les exigences qu’un type doit respecter pour qu’il s’agit d’un range
. Un range
itérateur fournit un itérateur et une sentinelle, afin que vous puissiez itérer sur ses éléments.
template<class T>
concept range = requires(T& rg)
{
ranges::begin(rg);
ranges::end(rg);
};
Paramètres
T
Type à tester pour voir s’il s’agit d’un range
.
Notes
Les exigences d’un range
sont les suivantes :
- Il peut être itéré à l’aide
std::ranges::begin()
etstd::ranges::end()
ranges::begin()
etranges::end()
s’exécutent en temps constant amorti et ne modifient pas lerange
. Le temps constant amorti ne signifie pas O(1), mais que le coût moyen sur une série d’appels, même dans le pire des cas, est O(n) plutôt que O(n^2) ou pire.[ranges::begin(), ranges::end())
indique une plage valide.
Simple_View
Il Simple_View
s’agit d’un concept d’exposition uniquement utilisé sur certaines ranges
interfaces. Elle n’est pas définie dans la bibliothèque. Il est utilisé uniquement dans la spécification pour décrire le comportement de certains adaptateurs de plage.
template<class V>
concept Simple_View = // exposition only
ranges::view<V> && ranges::range<const V> &&
std::same_as<std::ranges::iterator_t<V>, std::ranges::iterator_t<const V>> &&
std::same_as<std::ranges::sentinel_t<V>, std::ranges::sentinel_t<const V>>;
Paramètres
V
Type à tester pour voir s’il s’agit d’un Simple_View
.
Notes
Une vue V
est une Simple_View
valeur si toutes les valeurs suivantes sont vraies :
V
est une vueconst V
est une plage- Les deux
v
etconst V
ont les mêmes types d’itérateurs et de sentinelles.
sized_range
A sized_range
fournit le nombre d’éléments de la plage dans le temps constant amorti.
template<class T>
concept sized_range = range<T> &&
requires(T& t) { ranges::size(t); };
Paramètres
T
Type à tester pour voir s’il s’agit d’un sized_range
.
Notes
Les exigences d’un sized_range
sont celles qui l’appellent ranges::size
:
- Ne modifie pas la plage.
- Retourne le nombre d’éléments dans le temps constant amorti. Le temps constant amorti ne signifie pas O(1), mais que le coût moyen sur une série d’appels, même dans le pire des cas, est O(n) plutôt que O(n^2) ou pire.
Quelques exemples d’un sized_range
sont std::list
et std::vector
.
Exemple : sized_range
L’exemple suivant montre qu’il s’agit d’un vector
sized_range
:int
// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>
int main()
{
std::cout << std::boolalpha << std::ranges::sized_range<std::vector<int>> << '\n'; // outputs "true"
}
view
Un view
a un déplacement constant de la construction, de l’affectation et des opérations de destruction, quel que soit le nombre d’éléments qu’il possède. Il n’est pas nécessaire de copier des vues constructibles ou de copier assignables, mais si elles le sont, ces opérations doivent également s’exécuter en temps constant.
En raison de l’exigence de temps constant, vous pouvez composer efficacement des vues. Par exemple, étant donné un vecteur d’appel int
input
, une fonction qui détermine si un nombre est divisible par trois et une fonction qui place un nombre, l’instruction auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square);
produit efficacement une vue qui contient les carrés des nombres dans l’entrée qui sont divisibles par trois. La connexion de vues avec |
est appelée composition des vues. Si un type satisfait au view
concept, il peut être composé efficacement.
template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;
Paramètres
T
Type à tester pour voir s’il s’agit d’une vue.
Notes
L’exigence essentielle qui rend une vue composable est qu’il est bon marché de déplacer/copier. Cela est dû au fait que la vue est déplacée/copiée lorsqu’elle est composée avec une autre vue. Il doit s’agir d’une plage mobile.
ranges::enable_view<T>
est une caractéristique utilisée pour revendiquer la conformité aux exigences sémantiques du view
concept. Un type peut choisir par :
- dérivant publiquement et sans ambiguïté d’une spécialisation de
ranges::view_interface
- dérivant publiquement et sans ambiguïté de la classe
ranges::view_base
vide , ou ranges::enable_view<T>
spécialisé danstrue
L’option 1 est préférée, car view_interface
fournit également une implémentation par défaut qui enregistre du code réutilisable que vous devez écrire.
En cas d’échec, l’option 2 est un peu plus simple que l’option 3.
L’avantage de l’option 3 est qu’il est possible sans modifier la définition du type.
viewable_range
Il viewable_range
s’agit d’un type qui est une vue ou peut être converti en un.
template<class T>
concept viewable_range =
range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);
Paramètres
T
Type à tester pour voir s’il s’agit d’une vue ou peut être converti en un.
Notes
Permet std::ranges::views::all()
de convertir une plage en vue.