Constraints and concepts
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);
|
Dieser Abschnitt ist unvollständig
Grund: Template-Deklarationsseiten überarbeiten, um hierher zu verlinken |
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:
-
inlineundconstexprsind nicht erlaubt, die Funktion ist automatischinlineundconstexpr -
friendundvirtualsind 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
boolsein - 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
boolhaben - Kann nicht ohne Initialisierer deklariert werden
- Kann nicht auf Klassenebene deklariert werden
-
constexprist nicht erlaubt, die Variable ist automatischconstexpr - 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:
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:
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; }
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:
noexcept
verwendet wird, muss der Ausdruck ebenfalls noexcept sein (
Ausnahmebedingung
)
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
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
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
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
template<typename... Ts> concept bool C3 = true; C3{T} void q2(); // OK: <T> matches <...Ts> C3{...Ts} void q1(); // OK: <...Ts> matches <...Ts>
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
|
Dieser Abschnitt ist unvollständig
Grund: benötigt ein Beispiel mit aussagekräftigen Konzepten, nicht diese 'return true'-Platzhalter |
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
Asubsumiert das äquivalente atomare ConstraintA -
jede atomare Constraint
Asubsumiert eine DisjunktionA||Bund subsumiert keine KonjunktionA&&B -
jede Konjunktion
A&&BsubsumiertA, aber eine DisjunktionA||Bsubsumiert nichtA
Die Subsumptionsbeziehung definiert eine partielle Ordnung von Constraints, die verwendet wird, um zu bestimmen:
- der beste geeignete Kandidat für eine Nicht-Template-Funktion in der Überlagerungsauflösung
- die Adresse einer Nicht-Template-Funktion in einem Überlagerungssatz
- die beste Übereinstimmung für ein Template-Template-Argument
- partielle Ordnung von Klassentemplate-Spezialisierungen
- partielle Ordnung von Funktionstemplates
|
Dieser Abschnitt ist unvollständig
Grund: Rückverweise von oben nach hier |
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
Compiler-Unterstützung
GCC >= 6.1 unterstützt diese technische Spezifikation (erforderliche Option - fconcepts )