Namespaces
Variants

Constraints and concepts

From cppreference.net


Diese Seite beschreibt ein experimentelles Kernteil der Sprache. Für benannte Typanforderungen, die in der Spezifikation der Standardbibliothek verwendet werden, siehe named requirements

Klassentemplates , Funktionstemplates und nicht-templatisierte Funktionen (typischerweise Mitglieder von Klassentemplates) können mit einer Constraint assoziiert werden, die die Anforderungen an Template-Argumente spezifiziert. Diese kann verwendet werden, um die am besten geeigneten Funktionsüberladungen und Templatespezialisierungen auszuwählen.

Constraints können auch verwendet werden, um die automatische Typableitung in Variablendeklarationen und Funktionsrückgabetypen auf nur die Typen zu beschränken, die bestimmte Anforderungen erfüllen.

Benannte Mengen solcher Anforderungen werden als Konzepte bezeichnet. Jedes Konzept ist ein Prädikat, das zur Kompilierzeit ausgewertet wird und Teil der Schnittstelle einer Vorlage wird, in der es als Constraint verwendet wird:

#include <string>
#include <locale>
using namespace std::literals;
// Deklaration des Konzepts "EqualityComparable", das von jedem Typ T erfüllt wird,
// für den bei Werten a und b vom Typ T
// der Ausdruck a==b kompiliert und sein Ergebnis in bool konvertierbar ist
template<typename T>
concept bool EqualityComparable = requires(T a, T b) {
    { a == b } -> bool;
};
void f(EqualityComparable&&); // Deklaration einer eingeschränkten Funktionsvorlage
// template<typename T>
// void f(T&&) requires EqualityComparable<T>; // Lange Form desselben
int main() {
  f("abc"s); // OK, std::string ist EqualityComparable
  f(std::use_facet<std::ctype<char>>(std::locale{})); // Fehler: nicht EqualityComparable 
}

Verstöße gegen Constraints werden zur Kompilierzeit erkannt, früh im Template-Instanziierungsprozess, was zu leicht verständlichen Fehlermeldungen führt.

std::list<int> l = {3,-1,10};
std::sort(l.begin(), l.end()); 
//Typische Compiler-Diagnose ohne Konzepte:
//  invalid operands to binary expression ('std::_List_iterator<int>' and
//  'std::_List_iterator<int>')
//                           std::__lg(__last - __first) * 2);
//                                     ~~~~~~ ^ ~~~~~~~
// ... 50 Zeilen Ausgabe ...
//
//Typische Compiler-Diagnose mit Konzepten:
//  error: cannot call std::sort with std::_List_iterator<int>
//  note:  concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied

Die Absicht von Concepts ist es, semantische Kategorien (Number, Range, RegularFunction) zu modellieren und nicht syntaktische Einschränkungen (HasPlus, Array). Gemäß ISO C++ Core Guideline T.20 ist "die Fähigkeit, eine bedeutungsvolle Semantik zu spezifizieren, ein bestimmendes Merkmal eines echten Concepts im Gegensatz zu einer syntaktischen Einschränkung."

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

Inhaltsverzeichnis

Platzhalter

Der ungebundene Platzhalter auto und gebundene Platzhalter , welche die Form concept-name < template-argument-list (optional) > haben, sind Platzhalter für den abzuleitenden Typ.

Platzhalter können in Variablendeklarationen auftreten (in diesem Fall werden sie vom Initialisierer abgeleitet) oder in Funktionsrückgabetypen (in diesem Fall werden sie von return-Anweisungen abgeleitet)

std::pair<auto, auto> p2 = std::make_pair(0, 'a'); // erstes auto ist int,
                                                   // zweites auto ist char
Sortable x = f(y); // der Typ von x wird vom Rückgabetyp von f abgeleitet,
                   // kompiliert nur, wenn der Typ die Einschränkung Sortable erfüllt
auto f(Container) -> Sortable; // Rückgabetyp wird von der return-Anweisung abgeleitet
                               // kompiliert nur, wenn der Typ Sortable erfüllt

Platzhalter können auch in Parametern erscheinen, in welchem Fall sie Funktionsdeklarationen in Template-Deklarationen verwandeln (constrained, falls der Platzhalter constrained ist)

void f(std::pair<auto, EqualityComparable>); // dies ist ein Template mit zwei Parametern:
       // ungebundener Typ-Parameter und ein gebundener Nicht-Typ-Parameter

Eingeschränkte Platzhalter können überall verwendet werden, wo auto verwendet werden kann, zum Beispiel in generischen Lambda-Deklarationen

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

Wenn ein eingeschränkter Typspezifizierer einen Nicht-Typ oder eine Vorlage bezeichnet, aber als eingeschränkter Platzhalter verwendet wird, ist das Programm fehlerhaft:

template<size_t N> concept bool Even = (N%2 == 0);
struct S1 { int n; };
int Even::* p2 = &S1::n; // Fehler, ungültige Verwendung eines Nicht-Typ-Konzepts
void f(std::array<auto, Even>); // Fehler, ungültige Verwendung eines Nicht-Typ-Konzepts
template<Even N> void f(std::array<auto, N>); // OK

Abgekürzte Templates

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

// Kurzform
void g1(const EqualityComparable*, Incrementable&);
// Langform:
// template<EqualityComparable T, Incrementable U> void g1(const T*, U&);
// Erweiterte Form:
// template<typename T, typename U>
// void g1(const T*, U&) requires EqualityComparable<T> && Incrementable<U>;
void f2(std::vector<auto*>...);
// Langform: template<typename... T> void f2(std::vector<T*>...);
void f4(auto (auto::*)(auto));
// Langform: template<typename T, typename U, typename V> void f4(T (U::*)(V));

Alle Platzhalter, die durch äquivalente eingeschränkte Typspezifizierer eingeführt werden, haben denselben erfundenen Template-Parameter. Jedoch führt jeder uneingeschränkte Spezifizierer ( auto ) immer einen anderen Template-Parameter ein

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

Sowohl Funktionsvorlagen als auch Klassenvorlagen können mit einer Template-Einführung deklariert werden, die die Syntax Konzeptname { Parameterliste (optional) } hat, wobei das Schlüsselwort template nicht benötigt wird: Jeder Parameter aus der Parameterliste 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ädikat-Constraint (siehe unten), die den durch die Einführung benannten Concept benennt (für Variable Concepts) oder aufruft (für Function Concepts).

EqualityComparable{T} class Foo;
// lange Form: template<EqualityComparable T> class Foo;
// längere 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;
// lange Form template<class A, int B, class... C> requires Example<A,B,C...> struct S1;

Für Funktions-Templates kann die Template-Einführung mit Platzhaltern kombiniert werden:

Sortable{T} void f(T, auto);
// Lange Form: template<Sortable T, typename U> void f(T, U);
// Alternative nur mit Platzhaltern: void f(Sortable, auto);

Konzepte

Ein Konzept ist eine benannte Menge von Anforderungen. Die Definition eines Konzepts erscheint im Namensbereich und hat die Form einer Funktions-Template -Definition (in diesem Fall wird es Funktionskonzept genannt) oder einer Variablen-Template -Definition (in diesem Fall wird es Variablenkonzept genannt). Der einzige Unterschied besteht darin, dass das Schlüsselwort concept in der Deklarationsspezifizierer-Sequenz erscheint:

// Variablenkonzept aus der Standardbibliothek (Ranges TS)
template <class T, class U>
concept bool Derived = std::is_base_of<U, T>::value;
// Funktionskonzept aus der Standardbibliothek (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
  • Ausnahmespezifikation ist nicht erlaubt, die Funktion ist automatisch noexcept(true) .
  • kann nicht deklariert und später definiert werden, kann nicht erneut deklariert werden
  • der Rückgabetyp muss bool sein
  • Rückgabetyp-Ableitung ist nicht erlaubt
  • Parameterliste muss leer sein
  • der Funktionskörper muss nur aus einer return -Anweisung bestehen, deren Argument ein Constraint-Ausdruck sein muss (Prädikat-Constraint, Konjunktion/Disjunktion anderer Constraints oder ein Requires-Ausdruck, siehe unten)

Die folgenden Einschränkungen gelten für Variable Concepts:

  • Muss den Typ bool haben
  • Kann nicht ohne Initialisierer deklariert werden
  • Kann nicht auf Klassenebene deklariert werden
  • constexpr ist nicht erlaubt, die Variable ist automatisch constexpr
  • Der Initialisierer muss ein Constraint-Ausdruck sein (Prädikat-Constraint, Konjunktion/Disjunktion von Constraints oder ein Requires-Ausdruck, siehe unten)

Concepts können sich nicht rekursiv im Rumpf der Funktion oder im Initialisierer der Variable beziehen:

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

Explizite Instanziierungen, explizite Spezialisierungen oder partielle Spezialisierungen von Concepts sind nicht erlaubt (die Bedeutung der ursprünglichen Definition einer Constraint darf nicht verändert werden)

Einschränkungen

Eine Constraint ist eine Folge logischer Operationen, die Anforderungen an Template-Argumente spezifiziert. Sie können innerhalb von requires-expression s (siehe unten) und direkt als Rümpfe von Concepts auftreten.

Es gibt 9 Arten von Constraints:

1) Konjunktionen
2) Disjunktionen
3) Prädikat-Einschränkungen
4) Ausdrucksbeschränkungen (nur in einem requires-expression )
5) Typen-Einschränkungen (nur in einem requires-expression )
6) implizite Konvertierungsbeschränkungen (nur in einem requires-expression )
7) Argument-Deduktion-Einschränkungen (nur in einem requires-expression )
8) Exception-Bedingungen (nur in einem requires-expression )
9) parametrisierte Constraints (nur in einem requires-expression )

Die ersten drei Arten von Constraints können direkt als Rumpf eines Concepts oder als ad-hoc Requires-Klausel erscheinen:

template<typename T>
requires // requires-Klausel (ad-hoc-Constraint)
sizeof(T) > 1 && get_value<T>() // Konjunktion zweier Prädikat-Constraints
void f(T);

Wenn mehrere Constraints an dieselbe Deklaration angehängt werden, ist das Gesamt-Constraint eine Konjunktion in der folgenden Reihenfolge: das durch die Template-Einführung eingeführte Constraint, Constraints für jeden Template-Parameter in der Reihenfolge ihres Auftretens, die requires -Klausel nach der Template-Parameterliste, Constraints für jeden Funktionsparameter in der Reihenfolge ihres Auftretens, die nachgestellte requires -Klausel:

// die Deklarationen deklarieren dieselbe constrained Function Template
// mit der 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
// die folgenden zwei Deklarationen haben unterschiedliche Constraints:
// die erste Deklaration hat Incrementable<T> && Decrementable<T>
// die zweite Deklaration hat Decrementable<T> && Incrementable<T>
// Obwohl sie logisch äquivalent sind.
// Die zweite Deklaration ist ill-formed, keine Diagnose erforderlich
template<Incrementable T> requires Decrementable<T> void g();
template<Decrementable T> requires Incrementable<T> void g(); // error

Konjunktionen

Konjunktion von Constraints P und Q wird spezifiziert als P && Q .

// Beispielkonzepte aus der Standardbibliothek (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 Constraints ist nur dann erfüllt, wenn beide Constraints erfüllt sind. Konjunktionen werden von links nach rechts ausgewertet und kurzgeschlossen (wenn das linke Constraint nicht erfüllt ist, wird die Template-Argument-Substitution in das rechte Constraint nicht versucht: dies verhindert Fehler aufgrund von Substitution außerhalb des unmittelbaren Kontexts). Benutzerdefinierte Überladungen von operator&& sind in Constraint-Konjunktionen nicht erlaubt.

Disjunktionen

Disjunktion von Constraints P und Q wird spezifiziert als P || Q .

Eine Disjunktion von zwei Constraints ist erfüllt, wenn eines der beiden Constraints erfüllt ist. Disjunktionen werden von links nach rechts ausgewertet und kurzgeschlossen (wenn das linke Constraint erfüllt ist, wird keine Template-Argument-Deduktion in das rechte Constraint versucht). Benutzerdefinierte Überladungen von operator|| sind in Constraint-Disjunktionen nicht erlaubt.

// Beispiel-Constraint aus der Standardbibliothek (Ranges TS)
template <class T = void>
requires EqualityComparable<T>() || Same<T, void>
struct equal_to;

Prädikatsbeschränkungen

Eine Prädikatbedingung ist ein konstanter Ausdruck vom Typ bool . Sie ist nur erfüllt, wenn sie zu true ausgewertet wird.

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

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

Prädikat-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>{} // schlechte Prädikat-Einschränkung: S<T>{} ist nicht bool
void f(T);
f(0); // Fehler: Einschränkung niemals erfüllt

Anforderungen

Das Schlüsselwort requires wird auf zwei Arten verwendet:

1) Einführung einer requires-Klausel , die Einschränkungen für Template-Argumente oder für eine Funktionsdeklaration spezifiziert.
template<typename T>
void f(T&&) requires Eq<T>; // kann als letztes Element eines Funktionsdeklarators erscheinen
template<typename T> requires Addable<T> // oder direkt nach einer Template-Parameterliste
T add(T a, T b) { return a + b; }
In diesem Fall muss das Schlüsselwort requires von einem konstanten Ausdruck gefolgt werden (es ist also 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) Um einen requires-expression zu beginnen, welcher ein prvalue-Ausdruck vom Typ bool ist, der die Einschränkungen für einige Template-Argumente beschreibt. Ein solcher Ausdruck ist true , wenn das entsprechende Konzept erfüllt ist, andernfalls false:
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 des requires-expression lautet wie folgt:

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

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

  • einfache Anforderung
  • Typanforderungen
  • zusammengesetzte Anforderungen
  • verschachtelte Anforderungen

Anforderungen können sich auf die Template-Parameter beziehen, die im Gültigkeitsbereich liegen, und auf die lokalen Parameter, die in der parameter-list eingeführt werden. Wenn parametrisiert, wird gesagt, dass ein requires-Ausdruck eine parametrisierte Einschränkung einführt.

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 templated entity Deklaration verwendet wird, dann ist das Programm ill-formed.
  • Wenn der Requires-Ausdruck in einer Deklaration einer templated entity verwendet wird, wird die entsprechende Constraint als "nicht erfüllt" behandelt und der substitution failure is not an error , jedoch
  • Wenn ein Substitutionsfehler in einem Requires-Ausdruck für jedes mögliche Template-Argument auftreten würde, ist das Programm ill-formed, no diagnostic required:
template<class T> concept bool C = requires {
    new int[-(int)sizeof(T)]; // ungültig für jedes T: fehlerhaft, keine Diagnose erforderlich
};

Einfache Anforderungen

Eine einfache Anforderung ist eine beliebige Ausdrucksanweisung. Die Anforderung besteht darin, dass der Ausdruck gültig ist (dies ist eine expression constraint ). Im Gegensatz zu Prädikat-Constraints findet keine Auswertung statt, sondern nur die Sprachkorrektheit wird überprüft.

template<typename T>
concept bool Addable =
requires (T a, T b) {
    a + b; // "der Ausdruck a+b ist ein gültiger Ausdruck, der kompiliert wird"
};
// Beispiel-Constraint aus der Standardbibliothek (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));
};

Typanforderungen

Eine Typanforderung ist das Schlüsselwort typename gefolgt von einem Typnamen, optional qualifiziert. Die Anforderung besteht darin, dass der benannte Typ existiert (eine Typbeschränkung ): Dies kann verwendet werden, um zu überprüfen, dass ein bestimmter benannter geschachtelter Typ existiert, oder dass eine Klassentemplatespezialisierung einen Typ benennt, oder dass ein Aliastemplate einen Typ benennt.

template<typename T> using Ref = T&;
template<typename T> concept bool C =
requires {
    typename T::inner; // erforderlicher geschachtelter Member-Name
    typename S<T>;     // erforderliche Klassen-Template-Spezialisierung
    typename Ref<T>;   // erforderliche Alias-Template-Substitution
};
// Beispielkonzept aus der Standardbibliothek (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> ist gültig und benennt einen Typ
    { CommonType<T, U>{std::forward<T>(t)} }; 
    { CommonType<T, U>{std::forward<U>(u)} }; 
};

Zusammengesetzte Anforderungen

Eine zusammengesetzte Anforderung hat die Form

{ Ausdruck } noexcept (optional) Nachgestellter-Rückgabetyp (optional) ;

und gibt eine Konjunktion der folgenden Einschränkungen an:

1) expression ist ein gültiger Ausdruck ( expression constraint )
2) Wenn noexcept verwendet wird, muss der Ausdruck ebenfalls noexcept sein ( Ausnahmebedingung )
3) Wenn trailing-return-type einen Typ bezeichnet, der Platzhalter verwendet, muss der Typ aus dem Typ des Ausdrucks ableitbar sein ( Argumentdeduktionsbedingung )
4) Wenn der trailing-return-type einen Typ benennt, der keine Platzhalter verwendet, werden zwei weitere Constraints hinzugefügt:
4a) der von trailing-return-type benannte Typ ist gültig ( Typbedingung )
4b) das Ergebnis des Ausdrucks ist implizit konvertierbar zu diesem Typ ( implizite Konvertierungsbedingung )
template<typename T> concept bool C2 =
requires(T x) {
    {*x} -> typename T::inner; // der Ausdruck *x muss gültig sein
                               // UND der Typ T::inner muss gültig sein
                               // UND das Ergebnis von *x muss in T::inner konvertierbar sein
};
// Beispielkonzept aus der Standardbibliothek (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) }; // Direkte Initialisierungsbedingung muss Ausdruck verwenden
    { !b1 } -> bool; // zusammengesetzte Bedingung
    requires Same<decltype(b1 && b2), bool>; // verschachtelte Bedingung, siehe unten
    requires Same<decltype(b1 || b2), bool>;
};

Verschachtelte Anforderungen

Eine geschachtelte Anforderung ist eine weitere requires-Klausel , die mit einem Semikolon abgeschlossen wird. Dies wird verwendet, um Prädikat-Constraints einzuführen (siehe oben), die in Form anderer benannter Konzepte ausgedrückt werden, angewendet auf die lokalen Parameter (außerhalb einer requires-Klausel können Prädikat-Constraints keine Parameter verwenden, und das direkte Platzieren eines Ausdrucks in einer requires-Klausel macht ihn zu einem Ausdrucks-Constraint, was bedeutet, dass er nicht ausgewertet wird)

// Beispiel-Constraint aus 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)>;  // verschachtelt: "Same<...> ergibt true"
    { a.~T() } noexcept;  // zusammengesetzt: "a.~T()" ist ein gültiger Ausdruck, der keine Ausnahme wirft
    requires Same<T*, decltype(new T)>; // verschachtelt: "Same<...> ergibt true"
    requires Same<T*, decltype(new T[n])>; // verschachtelt
    { delete new T };  // zusammengesetzt
    { delete new T[n] }; // zusammengesetzt
};

Konzeptauflösung

Wie jede andere Funktionsvorlage kann ein Funktionskonzept (jedoch kein Variablenkonzept) überladen werden: Es können mehrere Konzeptdefinitionen bereitgestellt werden, die alle denselben concept-name verwenden.

Konzeptauflösung wird durchgeführt, wenn ein concept-name (der qualifiziert sein kann) erscheint in

1) ein eingeschränkter Typspezifizierer void f ( Concept ) ; std:: vector < Concept > x = ... ;
2) ein eingeschränkter Parameter template < Concept T > void f ( ) ;
3) eine Template-Einführung Concept { T } struct X ;
4) eine 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); // die Menge der Konzepte, auf die sich C bezieht, umfasst sowohl #1 als auch #2;
           // die Konzeptauflösung (siehe unten) wählt #1 aus.

Um Konzeptauflösung durchzuführen, template parameters jedes Konzepts, das mit dem Namen (und der Qualifikation, falls vorhanden) übereinstimmt, werden mit einer Sequenz von concept arguments abgeglichen, die aus Template-Argumenten und wildcards bestehen. Ein Wildcard kann mit einem Template-Parameter beliebiger Art (Typ, Nicht-Typ, Template) übereinstimmen. Der Argumentsatz 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 eine Parameterliste verwendet wird, ist die Argumentliste ein einzelner Platzhalter.
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 ein einzelner Platzhalter 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, die so lang ist 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 einer Template-ID erscheint, ist die Konzeptargumentliste genau die Sequenz der Argumente dieser 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

Konzeptauflösung wird durchgeführt, indem jedes Argument mit dem entsprechenden Parameter jedes sichtbaren Konzepts abgeglichen wird. Standard-Template-Argumente (falls verwendet) werden für jeden Parameter instanziiert, der keinem Argument entspricht, und dann an die Argumentliste angehängt. Ein Template-Parameter stimmt nur dann mit einem Argument überein, wenn er dieselbe Art (Typ, Nicht-Typ, Template) hat, es sei denn, das Argument ist ein Wildcard. Ein Parameter-Pack stimmt mit null oder mehr Argumenten überein, solange alle Argumente im Muster in der Art übereinstimmen (es sei denn, sie 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 anwendbar. Wenn null oder mehr als ein anwendbares Konzept vorhanden ist, ist das Programm fehlerhaft.

template<typename T> concept bool C2() { return true; }
template<int T> concept bool C2() { return true; }
template<C2<0> T> struct S1; // Fehler: <Platzhalter, 0> passt 
                             // weder auf <typename T> noch auf <int T>
template<C2 T> struct S2; // Sowohl #1 als auch #2 passen: Fehler

Partielle Ordnung von Constraints

Vor jeder weiteren Analyse werden Constraints normalisiert , indem der Körper jedes Namenskonzepts und jedes Requires-Ausdrucks substituiert wird, bis nur noch eine Sequenz von Konjunktionen und Disjunktionen atomarer Constraints übrig bleibt, die Prädikat-Constraints, Ausdrucks-Constraints, Typ-Constraints, implizite Konvertierungs-Constraints, Argumentdeduktions-Constraints und Ausnahme-Constraints sind.

Konzept P wird als subsumierend für Konzept Q bezeichnet, wenn bewiesen werden kann, dass P impliziert Q ohne Analyse von Typen und Ausdrücken auf Äquivalenz (daher N >= 0 subsumiert nicht N > 0 )

Konkret wird zunächst P in die disjunktive Normalform umgewandelt und Q in die konjunktive Normalform, und sie werden wie folgt verglichen:

  • jede atomare Constraint A subsumiert das äquivalente atomare Constraint A
  • jede atomare Constraint 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 Constraints, die verwendet wird, um zu bestimmen:

Wenn Deklarationen D1 und D2 eingeschränkt sind und D1s normalisierte Einschränkungen D2s normalisierte Einschränkungen subsumieren (oder wenn D1 eingeschränkt ist 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 subsumiert Decrementable, aber nicht umgekehrt
// RevIterator ist stärker eingeschränkt als Decrementable
void f(Decrementable); // #1
void f(RevIterator);   // #2
f(0);       // int erfüllt nur Decrementable, wählt #1
f((int*)0); // int* erfüllt beide Constraints, wählt #2 als stärker eingeschränkt
void g(auto);          // #3 (uneingeschränkt)
void g(Decrementable); // #4
g(true);  // bool erfüllt Decrementable nicht, wählt #3
g(0);     // int erfüllt Decrementable, wählt #4 da stärker eingeschränkt

Schlüsselwörter

concept , requires

Compiler-Unterstützung

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