Namensräume
Varianten
Aktionen

Einschränkungen und Konzepte

Von cppreference.com


Diese Seite beschreibt ein experimentelles Core-Language-Feature. Für benannte Typanforderungen, die in der Spezifikation der Standardbibliothek verwendet werden, siehe benannte Anforderungen

Klassentemplates, Funktionstemplates und Nicht-Template-Funktionen (typischerweise Member von Klassentemplates) können mit einer Einschränkung verbunden sein, die die Anforderungen an Template-Argumente spezifiziert, was verwendet werden kann, um die am besten geeigneten Funktionsüberladungen und Template-Spezialisierungen auszuwählen.

Einschränkungen können auch verwendet werden, um die automatische Typableitung bei Variablendeklarationen und Funktionsrückgabetypen auf nur die Typen zu beschränken, die spezifizierte Anforderungen erfüllen.

Benannte Anforderungssätze werden als Konzepte bezeichnet. Jedes Konzept ist ein zur Kompilierzeit ausgewertetes Prädikat und wird Teil der Schnittstelle eines Templates, in dem es als Einschränkung verwendet wird.

#include <string>
#include <locale>
using namespace std::literals;
 
// Declaration of the concept "EqualityComparable", which is satisfied by
// any type T such that for values a and b of type T,
// the expression a==b compiles and its result is convertible to bool
template<typename T>
concept bool EqualityComparable = requires(T a, T b) {
    { a == b } -> bool;
};
 
void f(EqualityComparable&&); // declaration of a constrained function template
// template<typename T>
// void f(T&&) requires EqualityComparable<T>; // long form of the same
 
int main() {
  f("abc"s); // OK, std::string is EqualityComparable
  f(std::use_facet<std::ctype<char>>(std::locale{})); // Error: not EqualityComparable 
}

Verletzungen von Einschränkungen werden zur Kompilierzeit, früh im Template-Instanziierungsprozess, erkannt, was zu leicht verständlichen Fehlermeldungen führt.

std::list<int> l = {3,-1,10};
std::sort(l.begin(), l.end()); 
//Typical compiler diagnostic without concepts:
//  invalid operands to binary expression ('std::_List_iterator<int>' and
//  'std::_List_iterator<int>')
//                           std::__lg(__last - __first) * 2);
//                                     ~~~~~~ ^ ~~~~~~~
// ... 50 lines of output ...
//
//Typical compiler diagnostic with concepts:
//  error: cannot call std::sort with std::_List_iterator<int>
//  note:  concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied

Die Absicht von Konzepten ist es, semantische Kategorien (Zahl, Bereich, Reguläre Funktion) anstelle von syntaktischen Beschränkungen (HatPlus, Array) zu modellieren. Gemäß der ISO C++ Core Guideline T.20 ist "die Fähigkeit, eine aussagekräftige Semantik anzugeben, ein definierendes Merkmal eines echten Konzepts im Gegensatz zu einer syntaktischen Einschränkung".

Wenn Feature-Testing unterstützt wird, werden die hier beschriebenen Features durch die Makrokonstante __cpp_concepts mit einem Wert von gleich oder größer 201507 angezeigt.

Inhalt

[edit] Platzhalter

Der uneingeschränkte Platzhalter auto und eingeschränkte Platzhalter, die die Form concept-name < template-argument-list(optional)> haben, sind Platzhalter für den Typ, der abgeleitet werden soll.

Platzhalter können in Variablendeklarationen (in diesem Fall werden sie aus dem Initialisierer abgeleitet) oder in Funktionsrückgabetypen (in diesem Fall werden sie aus Rückgabeanweisungen abgeleitet) vorkommen.

std::pair<auto, auto> p2 = std::make_pair(0, 'a'); // first auto is int,
                                                   // second auto is char
 
Sortable x = f(y); // the type of x is deduced from the return type of f, 
                   // only compiles if the type satisfies the constraint Sortable
 
auto f(Container) -> Sortable; // return type is deduced from the return statement
                               // only compiles if the type satisfies Sortable

Platzhalter können auch in Parametern vorkommen, in diesem Fall wandeln sie Funktionsdeklarationen in Template-Deklarationen um (eingeschränkt, wenn der Platzhalter eingeschränkt ist).

void f(std::pair<auto, EqualityComparable>); // this is a template with two parameters:
       // unconstrained type parameter and a constrained non-type parameter

Eingeschränkte Platzhalter können überall dort verwendet werden, wo auto verwendet werden kann, z. B. in generischen Lambda-Deklarationen.

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

Wenn ein eingeschränkter Typspezifizierer einen Nicht-Typ oder ein Template bezeichnet, aber als eingeschränkter Platzhalter verwendet wird, ist das Programm ill-formed.

template<size_t N> concept bool Even = (N%2 == 0);
struct S1 { int n; };
int Even::* p2 = &S1::n; // error, invalid use of a non-type concept
void f(std::array<auto, Even>); // error, invalid use of a non-type concept
template<Even N> void f(std::array<auto, N>); // OK

[edit] Abgekürzte Templates

Wenn ein oder mehrere Platzhalter in einer Funktionsparameterliste vorkommen, ist die Funktionsdeklaration tatsächlich eine Funktionstemplatedeklaration, deren Template-Parameterliste einen erfundenen Parameter für jeden eindeutigen Platzhalter in der Reihenfolge des Erscheinens enthält.

// short form
void g1(const EqualityComparable*, Incrementable&);
// long form:
// template<EqualityComparable T, Incrementable U> void g1(const T*, U&);
// longer form:
// template<typename T, typename U>
// void g1(const T*, U&) requires EqualityComparable<T> && Incrementable<U>;
 
void f2(std::vector<auto*>...);
// long form: template<typename... T> void f2(std::vector<T*>...);
 
void f4(auto (auto::*)(auto));
// long form: template<typename T, typename U, typename V> void f4(T (U::*)(V));

Alle von äquivalenten eingeschränkten Typspezifizierern eingeführten Platzhalter haben denselben erfundenen Template-Parameter. Jeder uneingeschränkte Spezifizierer (auto) führt jedoch immer einen anderen Template-Parameter ein.

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

Sowohl Funktionstemplates als auch Klassentemplates können mit einer Template-Einführung deklariert werden, die die Syntax concept-name { parameter-list(optional)} hat, in welchem Fall das Schlüsselwort template nicht benötigt wird: Jeder Parameter aus der parameter-list der Template-Einführung wird zu einem Template-Parameter, dessen Art (Typ, Nicht-Typ, Template) durch die Art des entsprechenden Parameters im benannten Konzept bestimmt wird.

Neben der Deklaration eines Templates assoziiert die Template-Einführung eine Prädikats-Einschränkung (siehe unten), die das durch die Einführung benannte Konzept benennt (bei Variablenkonzepten) oder aufruft (bei Funktionskonzepten).

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

Für Funktionstemplates kann die Template-Einführung mit Platzhaltern kombiniert werden.

Sortable{T} void f(T, auto);
// long form: template<Sortable T, typename U> void f(T, U);
// alternative using only placeholders: void f(Sortable, auto);

[edit] Konzepte

Ein Konzept ist ein benannter Satz von Anforderungen. Die Definition eines Konzepts erscheint im Namensraum und hat die Form einer Funktionstemplatedefinition (in diesem Fall wird es als Funktionskonzept bezeichnet) oder einer Variablentemplatedefinition (in diesem Fall wird es als Variablenkonzept bezeichnet). Der einzige Unterschied ist, dass das Schlüsselwort concept in der decl-specifier-seq erscheint.

// variable concept from the standard library (Ranges TS)
template <class T, class U>
concept bool Derived = std::is_base_of<U, T>::value;
 
// function concept from the standard library (Ranges TS)
template <class T>
concept bool EqualityComparable() { 
    return requires(T a, T b) { {a == b} -> Boolean; {a != b} -> Boolean; };
}

Die folgenden Einschränkungen gelten für Funktionskonzepte:

  • inline und constexpr sind nicht erlaubt, die Funktion ist automatisch inline und constexpr.
  • friend und virtual sind nicht erlaubt.
  • Ausnahmespezifikationen sind nicht erlaubt, die Funktion ist automatisch noexcept(true).
  • Kann nicht deklariert und später definiert werden, kann nicht neu deklariert werden.
  • Der Rückgabetyp muss bool sein.
  • Rückgabetypableitung ist nicht erlaubt.
  • Die Parameterliste muss leer sein.
  • Der Funktionsrumpf muss ausschließlich aus einer return-Anweisung bestehen, deren Argument ein constraint-expression (Prädikats-Einschränkung, Konjunktion/Disjunktion anderer Einschränkungen oder ein requires-Ausdruck, siehe unten) sein muss.

Die folgenden Einschränkungen gelten für Variablenkonzepte:

  • Muss den Typ bool haben.
  • Kann nicht ohne Initialisierer deklariert werden.
  • Kann nicht deklariert oder im Klassenbereich deklariert werden.
  • constexpr ist nicht erlaubt, die Variable ist automatisch constexpr.
  • Der Initialisierer muss ein Constraint-Ausdruck sein (Prädikats-Einschränkung, Konjunktion/Disjunktion von Einschränkungen oder ein requires-Ausdruck, siehe unten).

Konzepte dürfen sich im Körper der Funktion oder im Initialisierer der Variablen nicht rekursiv auf sich selbst beziehen.

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

Explizite Instanziierungen, explizite Spezialisierungen oder partielle Spezialisierungen von Konzepten sind nicht erlaubt (die Bedeutung der ursprünglichen Definition einer Einschränkung kann nicht geändert werden).

[edit] Einschränkungen

Eine Einschränkung ist eine Sequenz logischer Operationen, die Anforderungen an Template-Argumente spezifiziert. Sie können innerhalb von requires-expressions (siehe unten) und direkt als Körper von Konzepten erscheinen.

Es gibt 9 Arten von Einschränkungen:

1) Konjunktionen
2) Disjunktionen
3) Prädikats-Einschränkungen
4) Ausdruckseinschränkungen (nur in einem requires-expression)
5) Typ-Einschränkungen (nur in einem requires-expression)
6) implizite Konvertierungseinschränkungen (nur in einem requires-expression)
7) Argumentableitungseinschränkungen (nur in einem requires-expression)
8) Ausnahme-Einschränkungen (nur in einem requires-expression)
9) parametrisierte Einschränkungen (nur in einem requires-expression)

Die ersten drei Arten von Einschränkungen können direkt als Körper eines Konzepts oder als Ad-hoc-Requires-Klausel erscheinen.

template<typename T>
requires // requires-clause (ad-hoc constraint)
sizeof(T) > 1 && get_value<T>() // conjunction of two predicate constraints
void f(T);

Wenn mehrere Einschränkungen an dieselbe Deklaration angehängt sind, ist die Gesamteinschränkung eine Konjunktion in der folgenden Reihenfolge: die durch template introduction eingeführte Einschränkung, Einschränkungen für jeden Template-Parameter in der Reihenfolge des Erscheinens, die requires-Klausel nach der Template-Parameterliste, Einschränkungen für jeden Funktionsparameter in der Reihenfolge des Erscheinens, nachfolgende requires-Klausel.

// the declarations declare the same constrained function template 
// with the constraint 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
 
// the following two declarations have different constraints:
// the first declaration has Incrementable<T> && Decrementable<T>
// the second declaration has Decrementable<T> && Incrementable<T>
// Even though they are logically equivalent.
// The second declaration is ill-formed, no diagnostic required
 
template<Incrementable T> requires Decrementable<T> void g();
template<Decrementable T> requires Incrementable<T> void g(); // error

[edit] Konjunktionen

Die Konjunktion von Einschränkungen P und Q wird als P && Q spezifiziert.

// example concepts from the standard library (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>;

Eine Konjunktion zweier Einschränkungen ist nur dann erfüllt, wenn beide Einschränkungen erfüllt sind. Konjunktionen werden von links nach rechts ausgewertet und kurzgeschlossen (wenn die linke Einschränkung nicht erfüllt ist, wird die Template-Argument-Substitution in die rechte Einschränkung nicht versucht: dies verhindert Fehler aufgrund von Substitution außerhalb des unmittelbaren Kontexts). Benutzerdefinierte Überladungen von operator&& sind in Constraint-Konjunktionen nicht erlaubt.

[edit] Disjunktionen

Die Disjunktion von Einschränkungen P und Q wird als P || Q spezifiziert.

Eine Disjunktion zweier Einschränkungen ist erfüllt, wenn eine der beiden Einschränkungen erfüllt ist. Disjunktionen werden von links nach rechts ausgewertet und kurzgeschlossen (wenn die linke Einschränkung erfüllt ist, wird die Template-Argument-Ableitung in die rechte Einschränkung nicht versucht). Benutzerdefinierte Überladungen von operator|| sind in Constraint-Disjunktionen nicht erlaubt.

// example constraint from the standard library (Ranges TS)
template <class T = void>
requires EqualityComparable<T>() || Same<T, void>
struct equal_to;

[edit] Prädikats-Einschränkungen

Eine Prädikats-Einschränkung ist ein konstanter Ausdruck vom Typ bool. Sie ist nur dann erfüllt, wenn sie zu true ausgewertet wird.

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

Prädikats-Einschränkungen können Anforderungen an Nicht-Typ-Template-Parameter und an Template-Template-Argumente spezifizieren.

Prädikats-Einschränkungen müssen direkt zu bool ausgewertet werden, keine Konvertierungen erlaubt.

template<typename T> struct S {
    constexpr explicit operator bool() const { return true; }
};
template<typename T>
requires S<T>{} // bad predicate constraint: S<T>{} is not bool
void f(T);
f(0); // error: constraint never satisfied

[edit] Anforderungen

Das Schlüsselwort requires wird auf zwei Arten verwendet:

1) Zur Einführung einer requires-clause, die Einschränkungen für Template-Argumente oder für eine Funktionsdeklaration spezifiziert.
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; }
In diesem Fall muss das Schlüsselwort requires von einem konstanten Ausdruck gefolgt werden (daher ist es möglich, "requires true;" zu schreiben), aber die Absicht ist, dass ein benanntes Konzept (wie im obigen Beispiel) oder eine Konjunktion/Disjunktion von benannten Konzepten oder ein requires-expression verwendet wird.
2) Zum Beginn eines requires-expression, einem prvalue-Ausdruck vom Typ bool, der die Einschränkungen für einige Template-Argumente beschreibt. Ein solcher Ausdruck ist true, wenn das entsprechende Konzept erfüllt ist, und andernfalls falsch.
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; }

Die Syntax eines requires-expression ist wie folgt:

requires ( parameter-list(optional) ) { requirement-seq }
parameter-liste - eine durch Kommas getrennte Liste von Parametern wie in einer Funktionsdeklaration, außer dass Standardargumente nicht erlaubt sind und der letzte Parameter kein Ellipsis sein kann. Diese Parameter haben keinen Speicher, keine Linkage und keine Lebensdauer. Diese Parameter sind bis zum schließenden } der requirement-seq gültig. Wenn keine Parameter verwendet werden, können die runden Klammern ebenfalls weggelassen werden.
requirement-seq - eine durch Leerzeichen getrennte Sequenz von requirements, wie unten beschrieben (jede Anforderung endet mit einem Semikolon). Jede Anforderung fügt eine weitere Einschränkung zur Konjunktion von Einschränkungen hinzu, die dieser requires-Ausdruck definiert.

Jede Anforderung in der requirements-seq ist eine der folgenden:

  • einfache Anforderung
  • Typ-Anforderungen
  • zusammengesetzte Anforderungen
  • verschachtelte Anforderungen

Anforderungen können auf die in Scope befindlichen Template-Parameter und auf die lokalen Parameter verweisen, die in der parameter-list eingeführt wurden. Wenn ein requires-Ausdruck parametrisiert ist, wird er als Einführung einer parametrisierten Einschränkung bezeichnet.

Die Substitution von Template-Argumenten in einen requires-Ausdruck kann zur Bildung ungültiger Typen oder Ausdrücke in seinen Anforderungen führen. In solchen Fällen:

  • Wenn ein Substitutionsfehler in einem requires-Ausdruck auftritt, der außerhalb einer Deklaration einer templatisierten Entität verwendet wird, ist das Programm ill-formed.
  • Wenn der requires-Ausdruck in einer Deklaration einer templatisierten Entität verwendet wird, wird die entsprechende Einschränkung als "nicht erfüllt" behandelt und der Substitutionsfehler ist kein Fehler, jedoch:
  • Wenn ein Substitutionsfehler in einem requires-Ausdruck für alle möglichen Template-Argumente auftreten würde, ist das Programm ill-formed, keine Diagnose erforderlich.
template<class T> concept bool C = requires {
    new int[-(int)sizeof(T)]; // invalid for every T: ill-formed, no diagnostic required
};

[edit] Einfache Anforderungen

Eine einfache Anforderung ist eine beliebige Ausdrucksanweisung. Die Anforderung ist, dass der Ausdruck gültig ist (dies ist eine Ausdruckseinschränkung). Im Gegensatz zu Prädikats-Einschränkungen findet keine Auswertung statt, es wird nur die Sprachkorrektheit geprüft.

template<typename T>
concept bool Addable =
requires (T a, T b) {
    a + b; // "the expression a+b is a valid expression that will compile"
};
 
// example constraint from the standard library (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));
};

[edit] Typ-Anforderungen

Eine Typ-Anforderung ist das Schlüsselwort typename gefolgt von einem Typnamen, optional qualifiziert. Die Anforderung ist, dass der benannte Typ existiert (eine Typ-Einschränkung): Dies kann verwendet werden, um zu überprüfen, ob ein bestimmter benannter verschachtelter Typ existiert, oder ob ein Klassentemplate einen Typ benennt, oder ob ein Alias-Template einen Typ benennt.

template<typename T> using Ref = T&;
template<typename T> concept bool C =
requires {
    typename T::inner; // required nested member name
    typename S<T>;     // required class template specialization
    typename Ref<T>;   // required alias template substitution
};
 
//Example concept from the standard library (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> is valid and names a type
    { CommonType<T, U>{std::forward<T>(t)} }; 
    { CommonType<T, U>{std::forward<U>(u)} }; 
};

[edit] Zusammengesetzte Anforderungen

Eine zusammengesetzte Anforderung hat die Form:

{ expression } noexcept(optional) trailing-return-type(optional) ;

und spezifiziert eine Konjunktion der folgenden Einschränkungen:

1) expression ist ein gültiger Ausdruck (expression constraint)
2) Wenn noexcept verwendet wird, muss der Ausdruck auch noexcept sein (exception constraint)
3) Wenn trailing-return-type einen Typ benennt, der Platzhalter verwendet, muss der Typ aus dem Typ des Ausdrucks ableitbar sein (argument deduction constraint)
4) Wenn trailing-return-type einen Typ benennt, der keine Platzhalter verwendet, werden zwei weitere Einschränkungen hinzugefügt:
4a) der durch trailing-return-type benannte Typ ist gültig (type constraint)
4b) das Ergebnis des Ausdrucks ist implizit konvertierbar zu diesem Typ (implicit conversion constraint)
template<typename T> concept bool C2 =
requires(T x) {
    {*x} -> typename T::inner; // the expression *x must be valid
                               // AND the type T::inner must be valid
                               // AND the result of *x must be convertible to T::inner
};
 
// Example concept from the standard library (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) }; // direct initialization constraint has to use expression
    { !b1 } -> bool; // compound constraint
    requires Same<decltype(b1 && b2), bool>; // nested constraint, see below
    requires Same<decltype(b1 || b2), bool>;
};

[edit] Verschachtelte Anforderungen

Eine verschachtelte Anforderung ist eine weitere requires-clause, die mit einem Semikolon abgeschlossen wird. Dies wird verwendet, um Prädikats-Einschränkungen (siehe oben) einzuführen, die in Bezug auf andere benannte Konzepte, angewendet auf die lokalen Parameter, ausgedrückt werden (außerhalb einer requires-Klausel können Prädikats-Einschränkungen keine Parameter verwenden, und das direkte Platzieren eines Ausdrucks in einer requires-Klausel macht ihn zu einer Ausdruckseinschränkung, was bedeutet, dass er nicht ausgewertet wird).

// example constraint from 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)>;  // nested: "Same<...> evaluates to true"
    { a.~T() } noexcept;  // compound: "a.~T()" is a valid expression that doesn't throw
    requires Same<T*, decltype(new T)>; // nested: "Same<...> evaluates to true"
    requires Same<T*, decltype(new T[n])>; // nested
    { delete new T };  // compound
    { delete new T[n] }; // compound
};

[edit] Konzept-Auflösung

Wie jedes andere Funktionstemplate kann ein Funktionskonzept (aber kein Variablenkonzept) überladen werden: Mehrere Konzeptdefinitionen können bereitgestellt werden, die alle denselben concept-name verwenden.

Die Konzept-Auflösung wird durchgeführt, wenn ein concept-name (der qualifiziert sein kann) in Folgendem vorkommt:

1) einem eingeschränkten Typspezifizierer void f(Concept); std::vector<Concept> x = ...;
2) einem eingeschränkten Parameter template<Concept T> void f();
3) einer Template-Einführung Concept{T} struct X;
4) einem 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); // the set of concepts referred to by C includes both #1 and #2;
           // concept resolution (see below) selects #1.

Um die Konzept-Auflösung durchzuführen, werden die Template-Parameter jedes Konzepts, das mit dem Namen (und ggf. der Qualifikation) übereinstimmt, mit einer Sequenz von Konzeptargumenten abgeglichen, die Template-Argumente und Wildcards sind. Eine Wildcard kann einen Template-Parameter beliebiger Art (Typ, Nicht-Typ, Template) abgleichen. Der Argumentensatz wird je nach Kontext unterschiedlich konstruiert:

1) Für einen Konzeptnamen, der als Teil eines eingeschränkten Typspezifizierers oder Parameters verwendet wird, wenn der Konzeptname ohne Parameterliste verwendet wird, ist die Argumentliste eine einzelne Wildcard.
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) Für einen Konzeptnamen, der als Teil eines eingeschränkten Typspezifizierers oder Parameters verwendet wird, wenn der Konzeptname mit einer Template-Argumentliste verwendet wird, ist die Argumentliste eine einzelne Wildcard gefolgt von dieser Argumentliste.
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) Wenn ein Konzept in einer Template-Einführung erscheint, ist die Argumentliste eine Sequenz von Platzhaltern, so lang wie die Liste der Parameter in der Template-Einführung.
template<typename... Ts>
concept bool C3 = true;
C3{T} void q2();     // OK: <T> matches <...Ts>
C3{...Ts} void q1(); // OK: <...Ts> matches <...Ts>
4) Wenn ein Konzept als Name eines Template-IDs erscheint, ist die Konzept-Argumentliste genau die Sequenz der Argumente dieses Template-IDs.
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

Die Konzept-Auflösung erfolgt durch Abgleich jedes Arguments mit dem entsprechenden Parameter jedes sichtbaren Konzepts. Standard-Template-Argumente (falls verwendet) werden für jeden Parameter instanziiert, der keinem Argument entspricht, und dann der Argumentliste angehängt. Ein Template-Parameter gleicht ein Argument nur dann ab, wenn er die gleiche Art (Typ, Nicht-Typ, Template) hat, es sei denn, das Argument ist eine Wildcard. Ein Parameter-Pack gleicht null oder mehr Argumente ab, solange alle Argumente dem Muster in der Art entsprechen (es sei denn, es sind Wildcards).

Wenn ein Argument nicht mit seinem entsprechenden Parameter übereinstimmt oder wenn es mehr Argumente als Parameter gibt und der letzte Parameter kein Pack ist, ist das Konzept nicht lebensfähig. Wenn es null oder mehr als ein lebensfähiges Konzept gibt, ist das Programm ill-formed.

template<typename T> concept bool C2() { return true; }
template<int T> concept bool C2() { return true; }
 
template<C2<0> T> struct S1; // error: <wildcard, 0> matches 
                             // neither <typename T> nor <int T>
template<C2 T> struct S2; // both #1 and #2 match: error

[edit] Partielle Ordnung von Einschränkungen

Vor jeder weiteren Analyse werden Einschränkungen normalisiert, indem der Körper jedes benannten Konzepts und jeder requires-Ausdruck substituiert wird, bis eine Sequenz von Konjunktionen und Disjunktionen über atomare Einschränkungen übrig bleibt, welche Prädikats-Einschränkungen, Ausdruckseinschränkungen, Typ-Einschränkungen, implizite Konvertierungseinschränkungen, Argumentableitungseinschränkungen und Ausnahme-Einschränkungen sind.

Ein Konzept P subsumiert ein Konzept Q, wenn bewiesen werden kann, dass P Q impliziert, ohne Typen und Ausdrücke auf Äquivalenz zu analysieren (daher impliziert N >= 0 nicht N > 0).

Insbesondere wird P zuerst in die disjunktive Normalform und Q in die konjunktive Normalform umgewandelt und dann wie folgt verglichen:

  • jede atomare Einschränkung A subsumiert eine äquivalente atomare Einschränkung A.
  • jede atomare Einschränkung A subsumiert eine Disjunktion A||B und subsumiert keine Konjunktion A&&B.
  • jede Konjunktion A&&B subsumiert A, aber eine Disjunktion A||B subsumiert nicht A.

Die Subsumptionsbeziehung definiert eine partielle Ordnung von Einschränkungen, die zur Bestimmung von Folgendem verwendet wird:

Wenn Deklarationen D1 und D2 eingeschränkt sind und die normalisierten Einschränkungen von D1 die von D2 subsumieren (oder wenn D1 eingeschränkt und D2 uneingeschränkt ist), dann wird D1 als mindestens so eingeschränkt wie D2 bezeichnet. Wenn D1 mindestens so eingeschränkt wie D2 ist und D2 nicht mindestens so eingeschränkt wie D1 ist, dann ist D1 stärker eingeschränkt als D2.

template<typename T>
concept bool Decrementable = requires(T t) { --t; };
template<typename T>
concept bool RevIterator = Decrementable<T> && requires(T t) { *t; };
 
// RevIterator subsumes Decrementable, but not the other way around
// RevIterator is more constrained as Decrementable
 
void f(Decrementable); // #1
void f(RevIterator);   // #2
 
f(0);       // int only satisfies Decrementable, selects #1
f((int*)0); // int* satisfies both constraints, selects #2 as more constrained
 
void g(auto);          // #3 (unconstrained)
void g(Decrementable); // #4
 
g(true);  // bool does not satisfy Decrementable, selects #3
g(0);     // int satisfies Decrementable, selects #4 because it is more constrained

[edit] Schlüsselwörter

concept, requires

[edit] Compiler-Unterstützung

GCC >= 6.1 unterstützt diese technische Spezifikation (erforderliche Option -fconcepts)