Namespaces
Variants

Constraints and concepts

From cppreference.net


Cette page décrit une fonctionnalité expérimentale du langage de base. Pour les exigences de type nommées utilisées dans la spécification de la bibliothèque standard, consultez les exigences nommées

Modèles de classe , modèles de fonction , et les fonctions non-template (généralement membres de modèles de classe) peuvent être associés à une contrainte , qui spécifie les exigences sur les arguments template, qui peuvent être utilisées pour sélectionner les surcharges de fonction et les spécialisations de template les plus appropriées.

Les contraintes peuvent également être utilisées pour limiter la déduction automatique de type dans les déclarations de variables et les types de retour de fonction uniquement aux types qui satisfont aux exigences spécifiées.

Les ensembles nommés de telles exigences sont appelés concepts . Chaque concept est un prédicat, évalué à la compilation, et devient une partie de l'interface d'un modèle où il est utilisé comme contrainte :

#include <string>
#include <locale>
using namespace std::literals;
// Déclaration du concept "EqualityComparable", qui est satisfait par
// tout type T tel que pour les valeurs a et b de type T,
// l'expression a==b compile et son résultat est convertible en bool
template<typename T>
concept bool EqualityComparable = requires(T a, T b) {
    { a == b } -> bool;
};
void f(EqualityComparable&&); // déclaration d'un modèle de fonction contraint
// template<typename T>
// void f(T&&) requires EqualityComparable<T>; // forme longue équivalente
int main() {
  f("abc"s); // OK, std::string est EqualityComparable
  f(std::use_facet<std::ctype<char>>(std::locale{})); // Erreur : pas EqualityComparable 
}

Les violations des contraintes sont détectées au moment de la compilation, tôt dans le processus d'instanciation des templates, ce qui conduit à des messages d'erreur faciles à suivre.

std::list<int> l = {3,-1,10};
std::sort(l.begin(), l.end()); 
//Diagnostic typique du compilateur sans concepts :
//  invalid operands to binary expression ('std::_List_iterator<int>' and
//  'std::_List_iterator<int>')
//                           std::__lg(__last - __first) * 2);
//                                     ~~~~~~ ^ ~~~~~~~
// ... 50 lignes de sortie ...
//
//Diagnostic typique du compilateur avec concepts :
//  error: cannot call std::sort with std::_List_iterator<int>
//  note:  concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied

L'intention des concepts est de modéliser des catégories sémantiques (Number, Range, RegularFunction) plutôt que des restrictions syntaxiques (HasPlus, Array). Selon la directive centrale ISO C++ T.20 , "La capacité à spécifier une sémantique significative est une caractéristique déterminante d'un véritable concept, par opposition à une contrainte syntaxique."

Si la vérification des fonctionnalités est prise en charge, les fonctionnalités décrites ici sont indiquées par la constante macro __cpp_concepts avec une valeur égale ou supérieure à 201507 .

Table des matières

Espaces réservés

Le placeholder non contraint auto et les placeholders contraints qui ont la forme concept-name < template-argument-list (optional) > , sont des placeholders pour le type qui doit être déduit.

Les espaces réservés peuvent apparaître dans les déclarations de variables (auquel cas ils sont déduits de l'initialiseur) ou dans les types de retour de fonctions (auquel cas ils sont déduits des instructions return)

std::pair<auto, auto> p2 = std::make_pair(0, 'a'); // premier auto est int,
                                                   // deuxième auto est char
Sortable x = f(y); // le type de x est déduit du type de retour de f, 
                   // ne compile que si le type satisfait la contrainte Sortable
auto f(Container) -> Sortable; // le type de retour est déduit de l'instruction return
                               // ne compile que si le type satisfait Sortable

Les espaces réservés peuvent également apparaître dans les paramètres, auquel cas ils transforment les déclarations de fonctions en déclarations de modèles (contraintes si l'espace réservé est contraint)

void f(std::pair<auto, EqualityComparable>); // ceci est un modèle avec deux paramètres :
       // paramètre de type non contraint et paramètre non-type contraint

Les espaces réservés contraints peuvent être utilisés partout où auto peut être utilisé, par exemple, dans les déclarations de lambdas génériques

auto gl = [](Assignable& a, auto* b) { a = *b; };

Si un spécificateur de type contraint désigne un non-type ou un template, mais est utilisé comme un placeholder contraint, le programme est mal formé :

template<size_t N> concept bool Even = (N%2 == 0);
struct S1 { int n; };
int Even::* p2 = &S1::n; // erreur, utilisation invalide d'un concept non-type
void f(std::array<auto, Even>); // erreur, utilisation invalide d'un concept non-type
template<Even N> void f(std::array<auto, N>); // OK

Modèles abrégés

Si un ou plusieurs espaces réservés apparaissent dans une liste de paramètres de fonction, la déclaration de fonction est en réalité une déclaration de modèle de fonction, dont la liste de paramètres de modèle inclut un paramètre inventé pour chaque espace réservé unique, dans l'ordre d'apparition

// forme courte
void g1(const EqualityComparable*, Incrementable&);
// forme longue :
// template<EqualityComparable T, Incrementable U> void g1(const T*, U&);
// forme plus longue :
// template<typename T, typename U>
// void g1(const T*, U&) requires EqualityComparable<T> && Incrementable<U>;
void f2(std::vector<auto*>...);
// forme longue : template<typename... T> void f2(std::vector<T*>...);
void f4(auto (auto::*)(auto));
// forme longue : template<typename T, typename U, typename V> void f4(T (U::*)(V));

Tous les espaces réservés introduits par des spécificateurs de type équivalents contraints ont le même paramètre de template inventé. Cependant, chaque spécificateur non contraint ( auto ) introduit toujours un paramètre de template différent

void f0(Comparable a, Comparable* b);
// forme longue : template<Comparable T> void f0(T a, T* b);
void f1(auto a, auto* b);
// forme longue : template<typename T, typename U> f1(T a, U* b);

Les modèles de fonction et les modèles de classe peuvent tous deux être déclarés en utilisant une introduction de modèle , qui a la syntaxe nom-du-concept { liste-de-paramètres (optionnel) } , auquel cas le mot-clé template n'est pas nécessaire : chaque paramètre de la liste-de-paramètres de l'introduction de modèle devient un paramètre de modèle dont le type (type, non-type, modèle) est déterminé par le type du paramètre correspondant dans le concept nommé.

En plus de déclarer un template, l'introduction de template associe une contrainte de prédicat (voir ci-dessous) qui nomme (pour les concepts de variables) ou invoque (pour les concepts de fonctions) le concept nommé par l'introduction.

EqualityComparable{T} class Foo;
// forme longue : template<EqualityComparable T> class Foo;
// forme plus longue : template<typename T> requires EqualityComparable<T> class Foo;
template<typename T, int N, typename... Xs> concept bool Example = ...;
Example{A, B, ...C} struct S1;
// forme longue template<class A, int B, class... C> requires Example<A,B,C...> struct S1;

Pour les modèles de fonction, l'introduction de modèle peut être combinée avec des espaces réservés :

Sortable{T} void f(T, auto);
// forme longue : template<Sortable T, typename U> void f(T, U);
// alternative utilisant uniquement des espaces réservés : void f(Sortable, auto);

Concepts

Un concept est un ensemble nommé d'exigences. La définition d'un concept apparaît au niveau de l'espace de noms et prend la forme d'une fonction template (auquel cas il est appelé concept de fonction ) ou d'une variable template (auquel cas il est appelé concept de variable ). La seule différence est que le mot-clé concept apparaît dans la séquence de spécificateurs de déclaration :

// concept de variable de la bibliothèque standard (Ranges TS)
template <class T, class U>
concept bool Derived = std::is_base_of<U, T>::value;
// concept de fonction de la bibliothèque standard (Ranges TS)
template <class T>
concept bool EqualityComparable() { 
    return requires(T a, T b) { {a == b} -> Boolean; {a != b} -> Boolean; };
}

Les restrictions suivantes s'appliquent aux concepts de fonction :

  • inline et constexpr ne sont pas autorisés, la fonction est automatiquement inline et constexpr
  • friend et virtual ne sont pas autorisés
  • la spécification d'exception n'est pas autorisée, la fonction est automatiquement noexcept(true) .
  • ne peut pas être déclarée et définie ultérieurement, ne peut pas être redéclarée
  • le type de retour doit être bool
  • la déduction du type de retour n'est pas autorisée
  • la liste des paramètres doit être vide
  • le corps de la fonction doit uniquement contenir une instruction return , dont l'argument doit être une expression de contrainte (contrainte de prédicat, conjonction/disjonction d'autres contraintes, ou une expression requires, voir ci-dessous)

Les restrictions suivantes s'appliquent aux concepts de variable :

  • Doit avoir le type bool
  • Ne peut pas être déclaré sans initialiseur
  • Ne peut pas être déclaré au niveau de la classe
  • constexpr n'est pas autorisé, la variable est automatiquement constexpr
  • L'initialiseur doit être une expression de contrainte (contrainte de prédicat, conjonction/disjonction de contraintes, ou une expression requires, voir ci-dessous)

Les concepts ne peuvent pas se référer récursivement à eux-mêmes dans le corps de la fonction ou dans l'initialiseur de la variable :

template<typename T>
concept bool F() { return F<typename T::type>(); } // erreur
template<typename T>
concept bool V = V<T*>; // erreur

Les instanciations explicites, les spécialisations explicites ou les spécialisations partielles de concepts ne sont pas autorisées (la signification de la définition originale d'une contrainte ne peut pas être modifiée)

Contraintes

Une contrainte est une séquence d'opérations logiques qui spécifie des exigences sur les arguments de template. Elles peuvent apparaître dans des requires-expression s (voir ci-dessous) et directement comme corps de concepts

Il existe 9 types de contraintes :

1) conjonctions
2) disjonctions
3) contraintes de prédicat
4) contraintes d'expression (uniquement dans une requires-expression )
5) contraintes de type (uniquement dans une requires-expression )
6) contraintes de conversion implicite (uniquement dans une requires-expression )
7) contraintes de déduction d'arguments (uniquement dans une expression requires )
8) contraintes d'exception (uniquement dans une requires-expression )
9) contraintes paramétrées (uniquement dans une requires-expression )

Les trois premiers types de contraintes peuvent apparaître directement comme corps d'un concept ou comme une clause requires ad hoc :

template<typename T>
requires // clause requires (contrainte ad hoc)
sizeof(T) > 1 && get_value<T>() // conjonction de deux contraintes de prédicat
void f(T);

Lorsque plusieurs contraintes sont attachées à la même déclaration, la contrainte totale est une conjonction dans l'ordre suivant : la contrainte introduite par l'introduction de template , les contraintes pour chaque paramètre de template dans l'ordre d'apparition, la clause requires après la liste des paramètres de template, les contraintes pour chaque paramètre de fonction dans l'ordre d'apparition, la clause requires finale :

// les déclarations déclarent la même fonction template contrainte 
// avec la contrainte Incrementable<T> && Decrementable<T>
template<Incrementable T> void f(T) requires Decrementable<T>;
template<typename T> requires Incrementable<T> && Decrementable<T> void f(T); // ok
// les deux déclarations suivantes ont des contraintes différentes :
// la première déclaration a Incrementable<T> && Decrementable<T>
// la deuxième déclaration a Decrementable<T> && Incrementable<T>
// Même si elles sont logiquement équivalentes.
// La deuxième déclaration est mal formée, aucun diagnostic requis
template<Incrementable T> requires Decrementable<T> void g();
template<Decrementable T> requires Incrementable<T> void g(); // erreur

Conjonctions

Conjonction de contraintes P et Q est spécifiée comme P && Q .

// exemples de concepts de la bibliothèque standard (Ranges TS)
template <class T>
concept bool Integral = std::is_integral<T>::value;
template <class T>
concept bool SignedIntegral = Integral<T> && std::is_signed<T>::value;
template <class T>
concept bool UnsignedIntegral = Integral<T> && !SignedIntegral<T>;

Une conjonction de deux contraintes est satisfaite uniquement si les deux contraintes sont satisfaites. Les conjonctions sont évaluées de gauche à droite et court-circuitées (si la contrainte gauche n'est pas satisfaite, la substitution d'argument template dans la contrainte droite n'est pas tentée : cela empêche les échecs dus à la substitution en dehors du contexte immédiat). Les surcharges définies par l'utilisateur de operator&& ne sont pas autorisées dans les conjonctions de contraintes.

Disjonctions

Disjonction de contraintes P et Q est spécifiée comme P || Q .

Une disjonction de deux contraintes est satisfaite si l'une ou l'autre contrainte est satisfaite. Les disjonctions sont évaluées de gauche à droite et court-circuitées (si la contrainte de gauche est satisfaite, la déduction d'argument de template dans la contrainte de droite n'est pas tentée). Les surcharges définies par l'utilisateur de operator|| ne sont pas autorisées dans les disjonctions de contraintes.

// exemple de contrainte de la bibliothèque standard (Ranges TS)
template <class T = void>
requires EqualityComparable<T>() || Same<T, void>
struct equal_to;

Contraintes de prédicat

Une contrainte de prédicat est une expression constante de type bool . Elle est satisfaite uniquement si elle s'évalue à true

template<typename T> concept bool Size32 = sizeof(T) == 4;

Les contraintes de prédicat peuvent spécifier des exigences sur les paramètres de modèle non typés et sur les arguments de modèle de modèle.

Les contraintes de prédicat doivent s'évaluer directement en bool , aucune conversion n'est autorisée :

template<typename T> struct S {
    constexpr explicit operator bool() const { return true; }
};
template<typename T>
requires S<T>{} // contrainte de prédicat incorrecte : S<T>{} n'est pas bool
void f(T);
f(0); // erreur : contrainte jamais satisfaite

Exigences

Le mot-clé requires est utilisé de deux manières :

1) Pour introduire une clause requires , qui spécifie des contraintes sur les arguments de template ou sur une déclaration de fonction.
template<typename T>
void f(T&&) requires Eq<T>; // can appear as the last element of a function declarator
template<typename T> requires Addable<T> // or right after a template parameter list
T add(T a, T b) { return a + b; }
Dans ce cas, le mot-clé requires doit être suivi d'une expression constante (il est donc possible d'écrire "requires true;"), mais l'intention est qu'un concept nommé (comme dans l'exemple ci-dessus) ou une conjonction/disjonction de concepts nommés ou une requires-expression soit utilisée.
2) Pour commencer une requires-expression , qui est une expression prvalue de type bool qui décrit les contraintes sur certains arguments de template. Cette expression est true si le concept correspondant est satisfait, et false sinon :
template<typename T>
concept bool Addable = requires (T x) { x + x; }; // requires-expression
template<typename T> requires Addable<T> // requires-clause, not requires-expression
T add(T a, T b) { return a + b; }
template<typename T>
requires requires (T x) { x + x; } // ad-hoc constraint, note keyword used twice
T add(T a, T b) { return a + b; }

La syntaxe de l' expression requires est la suivante :

requires ( liste-de-paramètres (optionnel) ) { séquence-d'exigences }
parameter-list - une liste séparée par des virgules de paramètres comme dans une déclaration de fonction, sauf que les arguments par défaut ne sont pas autorisés et le dernier paramètre ne peut pas être une ellipse. Ces paramètres n'ont pas de stockage, de liaison ou de durée de vie. Ces paramètres sont dans la portée jusqu'à la fermeture } du requirement-seq . Si aucun paramètre n'est utilisé, les parenthèses rondes peuvent également être omises
requirement-seq - une séquence séparée par des espaces de exigences , décrites ci-dessous (chaque exigence se termine par un point-virgule). Chaque exigence ajoute une autre contrainte à la conjonction de contraintes que cette expression de nécessité définit.

Chaque exigence dans la requirements-seq est l'une des suivantes :

  • exigence simple
  • exigences de type
  • exigences composées
  • exigences imbriquées

Les exigences peuvent faire référence aux paramètres de modèle qui sont dans la portée et aux paramètres locaux introduits dans la liste-de-paramètres . Lorsqu'elle est paramétrée, une expression de contrainte est dite introduire une contrainte paramétrée

La substitution des arguments de template dans une expression requires peut entraîner la formation de types ou d'expressions invalides dans ses exigences. Dans de tels cas,

  • Si un échec de substitution se produit dans une expression requires utilisée en dehors d'une entité templatée déclaration, alors le programme est mal formé.
  • Si l'expression requires est utilisée dans une déclaration d'une entité templatée , la contrainte correspondante est traitée comme "non satisfaite" et l'échec de substitution n'est pas une erreur , cependant
  • Si un échec de substitution se produirait dans une expression requires pour chaque argument de template possible, le programme est mal formé, aucun diagnostic requis :
template<class T> concept bool C = requires {
    new int[-(int)sizeof(T)]; // invalide pour tout T : mal formé, aucun diagnostic requis
};

Exigences simples

Une exigence simple est une instruction d'expression arbitraire. L'exigence est que l'expression soit valide (c'est une contrainte d'expression ). Contrairement aux contraintes de prédicat, l'évaluation n'a pas lieu, seule la correction syntaxique est vérifiée.

template<typename T>
concept bool Addable =
requires (T a, T b) {
    a + b; // "l'expression a+b est une expression valide qui sera compilée"
};
// exemple de contrainte de la bibliothèque standard (ranges TS)
template <class T, class U = T>
concept bool Swappable = requires(T&& t, U&& u) {
    swap(std::forward<T>(t), std::forward<U>(u));
    swap(std::forward<U>(u), std::forward<T>(t));
};

Exigences de type

Une exigence de type est le mot-clé typename suivi d'un nom de type, éventuellement qualifié. L'exigence est que le type nommé existe (une contrainte de type ) : cela peut être utilisé pour vérifier qu'un certain type imbriqué nommé existe, ou qu'une spécialisation de modèle de classe désigne un type, ou qu'un modèle d'alias désigne un type.

template<typename T> using Ref = T&;
template<typename T> concept bool C =
requires {
    typename T::inner; // nom de membre imbriqué requis
    typename S<T>;     // spécialisation de template de classe requise
    typename Ref<T>;   // substitution de template d'alias requise
};
// Exemple de concept de la bibliothèque standard (Ranges TS)
template <class T, class U> using CommonType = std::common_type_t<T, U>;
template <class T, class U> concept bool Common =
requires (T t, U u) {
    typename CommonType<T, U>; // CommonType<T, U> est valide et désigne un type
    { CommonType<T, U>{std::forward<T>(t)} }; 
    { CommonType<T, U>{std::forward<U>(u)} }; 
};

Exigences Composées

Une exigence composée a la forme

{ expression } noexcept (optionnel) type-retour-final (optionnel) ;

et spécifie une conjonction des contraintes suivantes :

1) expression est une expression valide ( contrainte d'expression )
2) Si noexcept est utilisé, l'expression doit également être noexcept ( contrainte d'exception )
3) Si le trailing-return-type désigne un type qui utilise des espaces réservés, le type doit être déductible à partir du type de l'expression ( contrainte de déduction d'argument )
4) Si le trailing-return-type désigne un type qui n'utilise pas d'espaces réservés, alors deux contraintes supplémentaires sont ajoutées :
4a) le type désigné par trailing-return-type est valide ( type constraint )
4b) le résultat de l'expression est implicitement convertible en ce type ( contrainte de conversion implicite )
template<typename T> concept bool C2 =
requires(T x) {
    {*x} -> typename T::inner; // l'expression *x doit être valide
                               // ET le type T::inner doit être valide
                               // ET le résultat de *x doit être convertible en T::inner
};
// Exemple de concept de la bibliothèque standard (Ranges TS)
template <class T, class U> concept bool Same = std::is_same<T,U>::value;
template <class B> concept bool Boolean =
requires(B b1, B b2) {
    { bool(b1) }; // la contrainte d'initialisation directe doit utiliser une expression
    { !b1 } -> bool; // contrainte composée
    requires Same<decltype(b1 && b2), bool>; // contrainte imbriquée, voir ci-dessous
    requires Same<decltype(b1 || b2), bool>;
};

Exigences imbriquées

Une exigence imbriquée est une autre clause requires , terminée par un point-virgule. Ceci est utilisé pour introduire des contraintes de prédicat (voir ci-dessus) exprimées en termes d'autres concepts nommés appliqués aux paramètres locaux (en dehors d'une clause requires, les contraintes de prédicat ne peuvent pas utiliser de paramètres, et placer une expression directement dans une clause requires en fait une contrainte d'expression ce qui signifie qu'elle n'est pas évaluée)

// exemple de contrainte du Ranges TS
template <class T>
concept bool Semiregular = DefaultConstructible<T> &&
    CopyConstructible<T> && Destructible<T> && CopyAssignable<T> &&
requires(T a, size_t n) {  
    requires Same<T*, decltype(&a)>;  // imbriquée : "Same<...> évalue à true"
    { a.~T() } noexcept;  // composée : "a.~T()" est une expression valide qui ne lève pas d'exception
    requires Same<T*, decltype(new T)>; // imbriquée : "Same<...> évalue à true"
    requires Same<T*, decltype(new T[n])>; // imbriquée
    { delete new T };  // composée
    { delete new T[n] }; // composée
};

Résolution de concept

Comme toute autre fonction template, un concept de fonction (mais pas un concept de variable) peut être surchargé : plusieurs définitions de concept peuvent être fournies qui utilisent toutes le même concept-name .

La résolution de concept est effectuée lorsqu'un concept-name (qui peut être qualifié) apparaît dans

1) un spécificateur de type contraint void f ( Concept ) ; std:: vector < Concept > x = ... ;
2) un paramètre contraint template < Concept T > void f ( ) ;
3) une introduction aux templates Concept { T } struct X ;
4) un constraint-expression template < typename T > void f ( ) requires Concept < T > ;
template<typename T> concept bool C() { return true; } // #1
template<typename T, typename U> concept bool C() { return true; } // #2
void f(C); // l'ensemble des concepts référencés par C inclut à la fois #1 et #2 ;
           // la résolution de concept (voir ci-dessous) sélectionne #1.

Afin d'effectuer la résolution de concept, template parameters de chaque concept correspondant au nom (et à la qualification, le cas échéant) est mis en correspondance avec une séquence d' concept arguments , qui sont des arguments de template et des wildcards . Un wildcard peut correspondre à un paramètre de template de tout type (type, non-type, template). L'ensemble d'arguments est construit différemment, selon le contexte

1) Pour un nom de concept utilisé comme partie d'un spécificateur de type contraint ou d'un paramètre, si le nom du concept est utilisé sans liste de paramètres, la liste d'arguments est un unique caractère générique.
template<typename T> concept bool C1() { return true; } // #1
template<typename T, typename U> concept bool C1() { return true; } // #2
void f1(const C1*); // <wildcard> matches <T>, selects #1
2) Pour un nom de concept utilisé comme partie d'un spécificateur de type contraint ou paramètre, si le nom de concept est utilisé avec une liste d'arguments template, la liste d'arguments est un caractère générique unique suivi de cette liste d'arguments.
template<typename T> concept bool C1() { return true; } // #1
template<typename T, typename U> concept bool C1() { return true; } // #2
void f2(C1<char>); // <wildcard, char> matches <T, U>, selects #2
3) Si un concept apparaît dans une introduction de template, la liste d'arguments est une séquence d'espaces réservés aussi longue que la liste des paramètres dans l'introduction du template
template<typename... Ts>
concept bool C3 = true;
C3{T} void q2();     // OK: <T> matches <...Ts>
C3{...Ts} void q1(); // OK: <...Ts> matches <...Ts>
4) Si un concept apparaît comme nom d'un template-id, la liste d'arguments du concept est exactement la séquence d'arguments de ce template-id
template<typename T> concept bool C() { return true; } // #1
template<typename T, typename U> concept bool C() { return true; } // #2
template <typename T>
void f(T) requires C<T>(); // matches #1

La résolution de concept est effectuée en faisant correspondre chaque argument au paramètre correspondant de chaque concept visible. Les arguments de template par défaut (s'ils sont utilisés) sont instanciés pour chaque paramètre qui ne correspond pas à un argument, puis sont ajoutés à la liste d'arguments. Un paramètre de template correspond à un argument seulement s'il a la même nature (type, non-type, template), sauf si l'argument est un caractère générique. Un paramètre pack correspond à zéro ou plusieurs arguments tant que tous les arguments correspondent au modèle en nature (sauf s'ils sont des caractères génériques).

Si un argument ne correspond pas à son paramètre correspondant ou s'il y a plus d'arguments que de paramètres et que le dernier paramètre n'est pas un pack, le concept n'est pas viable. S'il y a zéro ou plus d'un concept viable, le programme est mal formé.

template<typename T> concept bool C2() { return true; }
template<int T> concept bool C2() { return true; }
template<C2<0> T> struct S1; // erreur : <wildcard, 0> ne correspond 
                             // ni à <typename T> ni à <int T>
template<C2 T> struct S2; // les deux #1 et #2 correspondent : erreur

Ordonnancement partiel des contraintes

Avant toute analyse supplémentaire, les contraintes sont normalisées en substituant le corps de chaque concept nommé et chaque expression requires jusqu'à ce qu'il ne reste qu'une séquence de conjonctions et de disjonctions sur des contraintes atomiques, qui sont des contraintes de prédicat, des contraintes d'expression, des contraintes de type, des contraintes de conversion implicite, des contraintes de déduction d'arguments et des contraintes d'exception.

Le concept P est dit subsumer le concept Q s'il peut être prouvé que P implique Q sans analyser les types et expressions pour l'équivalence (donc N >= 0 ne subsume pas N > 0 )

Plus précisément, d'abord P est converti en forme normale disjonctive et Q est converti en forme normale conjonctive, puis ils sont comparés comme suit :

  • chaque contrainte atomique A subsume la contrainte atomique équivalente A
  • chaque contrainte atomique A subsume une disjonction A||B et ne subsume pas une conjonction A&&B
  • chaque conjonction A&&B subsume A , mais une disjonction A||B ne subsume pas A

La relation de subsomption définit un ordre partiel des contraintes, qui est utilisé pour déterminer :

Si les déclarations D1 et D2 sont contraintes et que les contraintes normalisées de D1 englobent les contraintes normalisées de D2 (ou si D1 est contrainte et D2 est non contrainte), alors D1 est dite au moins aussi contrainte que D2. Si D1 est au moins aussi contrainte que D2 et que D2 n'est pas au moins aussi contrainte que D1, alors D1 est plus contrainte que D2.

template<typename T>
concept bool Decrementable = requires(T t) { --t; };
template<typename T>
concept bool RevIterator = Decrementable<T> && requires(T t) { *t; };
// RevIterator englobe Decrementable, mais pas l'inverse
// RevIterator est plus contraint que Decrementable
void f(Decrementable); // #1
void f(RevIterator);   // #2
f(0);       // int satisfait seulement Decrementable, sélectionne #1
f((int*)0); // int* satisfait les deux contraintes, sélectionne #2 comme plus contraint
void g(auto);          // #3 (non contraint)
void g(Decrementable); // #4
g(true);  // bool ne satisfait pas Decrementable, sélectionne #3
g(0);     // int satisfait Decrementable, sélectionne #4 car il est plus contraint

Mots-clés

concept , requires

Support du compilateur

GCC >= 6.1 prend en charge cette spécification technique (option requise - fconcepts )