Partager via


<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_rangeinput_rangeet 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.

Diagramme de la hiérarchie d’itérateur de plages. input_range et output_range sont les itérateurs les plus élémentaires. forward_range est ensuite affiné à la fois input_range et output_range. bidirectional_range affine forward_range. random_access_range affine bidirectional_range. Enfin, contiguous_range affine random_access_range

Voici d’autres concepts de plage :

Concept de plage Description
rangeC++20 Spécifie un type qui fournit un itérateur et une sentinelle.
borrowed_rangeC++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_rangeC++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_ViewC++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_rangeC++20 Spécifie une plage qui peut fournir son nombre d’éléments efficacement.
viewC++20 Spécifie un type qui a un déplacement efficace (temps constant) de construction, d’affectation et de destruction.
viewable_rangeC++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::vectoret 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::vectoret 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 un input_iterator. Appeler begin() plusieurs fois sur un input_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. Un input_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_rangeforward_range, et bidirectional_range. A random_access_range est triable.

Quelques exemples d’un random_access_range sont std::vector, std::arrayet 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() et std::ranges::end()
  • ranges::begin() et ranges::end() s’exécutent en temps constant amorti et ne modifient pas le range. 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 vue
  • const V est une plage
  • Les deux v et const 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_basevide , 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.

Voir aussi

<ranges>
Adaptateurs de plage
Afficher les classes