Class template argument deduction (CTAD) (since C++17)
Um eine Klassenvorlage zu instanziieren, müssen alle Template-Argumente bekannt sein, aber nicht jedes Template-Argument muss angegeben werden. In den folgenden Kontexten leitet der Compiler die Template-Argumente aus dem Typ des Initialisierers ab:
- jede Deklaration die eine Initialisierung einer Variable und Variablen-Template spezifiziert, deren deklarierter Typ das Klassentemplate ist (möglicherweise cv-qualifiziert ):
std::pair p(2, 4.5); // ergibt std::pair<int, double> p(2, 4.5); std::tuple t(4, 3, 2.5); // identisch mit auto t = std::make_tuple(4, 3, 2.5); std::less l; // identisch mit std::less<void> l;
template<class T> struct A { A(T, T); }; auto y = new A{1, 2}; // zugewiesener Typ ist A<int>
- Funktionsstil-Cast Ausdrücke:
auto lck = std::lock_guard(mtx); // leitet sich zu std::lock_guard<std::mutex> ab std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); // leitet sich zu std::back_insert_iterator<T> ab, // wobei T der Typ des Containers vi2 ist std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // leitet sich zu Foo<T> ab, // wobei T der eindeutige Lambda-Typ ist
template<class T> struct X { constexpr X(T) {} }; template<X x> struct Y {}; Y<0> y; // OK, Y<X<int>(0)> |
(seit C++20) |
Inhaltsverzeichnis |
Deduktion für Klassentemplates
Implizit generierte Deduction Guides
Wenn in einem Funktionsstil-Cast oder in der Deklaration einer Variable der Typspezifizierer ausschließlich aus dem Namen einer primären Klassenvorlage
C
besteht (d.h., es gibt keine begleitende Template-Argumentliste), werden Kandidaten für die Ableitung wie folgt gebildet:
-
Falls
Cdefiniert ist, wird für jeden Konstruktor (oder Konstruktortemplate)C i, der in der benannten primären Template deklariert ist, eine fiktive FunktionstemplateF ikonstruiert, sodass alle folgenden Bedingungen erfüllt sind:
-
-
Die Template-Parameter von
F isind die Template-Parameter vonCgefolgt (fallsC iein Konstruktor-Template ist) von den Template-Parametern vonC i(Standard-Template-Argumente sind ebenfalls enthalten).
-
Die Template-Parameter von
|
(seit C++20) |
-
-
Die
Parameterliste
von
F iist die Parameterliste vonC i. -
Der Rückgabetyp von
F iistCgefolgt von den Template-Parametern der Klassenvorlage, eingeschlossen in<>.
-
Die
Parameterliste
von
-
Wenn
Cnicht definiert ist oder keine Konstruktoren deklariert, wird eine zusätzliche fiktive Funktionsvorlage hinzugefügt, die wie oben von einem hypothetischen KonstruktorC()abgeleitet wird.
-
In jedem Fall wird eine zusätzliche fiktive Funktionsvorlage, die wie oben von einem hypothetischen Konstruktor
C(C)abgeleitet wurde, hinzugefügt, genannt der Copy Deduction Candidate.
-
Für jeden
benutzerdefinierten Ableitungsleitfaden
G iwird eine fiktive Funktion oder FunktionsvorlageF ikonstruiert, sodass alle folgenden Bedingungen erfüllt sind:
-
-
Die Parameterliste von
F iist die Parameterliste vonG i. -
Der Rückgabetyp von
F iist die einfache Template-Kennung vonG i. -
Wenn
G iTemplate-Parameter besitzt (Syntax (2) ),F iist ein Funktions-Template, und seine Template-Parameterliste ist die Template-Parameterliste vonG i. Andernfalls istF ieine Funktion.
-
Die Parameterliste von
template<class T> struct A { T t; struct { long a, b; } u; }; A a{1, 2, 3}; // aggregate deduction candidate: // template<class T> // A<T> F(T, long, long); template<class... Args> struct B : std::tuple<Args...>, Args... {}; B b{std::tuple<std::any, std::string>{}, std::any{}}; // aggregate deduction candidate: // template<class... Args> // B<Args...> F(std::tuple<Args...>, Args...); // type of b is deduced as B<std::any, std::string> |
(seit C++20) |
Template Argument Deduction
und
Overload Resolution
werden dann zur Initialisierung eines fiktiven Objekts eines hypothetischen Klassentyps durchgeführt, dessen Konstruktorsignaturen mit den Guides übereinstimmen (außer im Rückgabetyp), um einen Overload-Satz zu bilden. Der Initialisierer wird durch den Kontext bereitgestellt, in dem die Klassentemplate-Argumentableitung durchgeführt wurde, mit der Ausnahme, dass die erste Phase der
Listeninitialisierung
(Betrachtung von Initializer-List-Konstruktoren) weggelassen wird, wenn die Initialisierungsliste aus einem einzelnen Ausdruck vom Typ (möglicherweise cv-qualifiziert)
U
besteht, wobei
U
eine Spezialisierung von
C
oder eine von einer Spezialisierung von
C
abgeleitete Klasse ist.
Diese fiktiven Konstruktoren sind öffentliche Mitglieder des hypothetischen Klassentyps. Sie sind explizit, wenn der Leitfaden aus einem expliziten Konstruktor gebildet wurde. Wenn die Überladungsauflösung fehlschlägt, ist das Programm fehlerhaft. Andernfalls wird der Rückgabetyp der ausgewählten
F
-Template-Spezialisierung zur deduzierten Klassentemplate-Spezialisierung.
template<class T> struct UniquePtr { UniquePtr(T* t); }; UniquePtr dp{new auto(2.0)}; // Ein deklarierter Konstruktor: // C1: UniquePtr(T*); // Menge implizit generierter Ableitungsleitfäden: // F1: template<class T> // UniquePtr<T> F(T* p); // F2: template<class T> // UniquePtr<T> F(UniquePtr<T>); // Kopier-Ableitungskandidat // Imaginäre Klasse zur Initialisierung: // struct X // { // template<class T> // X(T* p); // von F1 // // template<class T> // X(UniquePtr<T>); // von F2 // }; // Direktinitialisierung eines X-Objekts // mit "new double(2.0)" als Initialisierer // wählt den Konstruktor, der dem Leitfaden F1 mit T = double entspricht // Für F1 mit T=double ist der Rückgabetyp UniquePtr<double> // Ergebnis: // UniquePtr<double> dp{new auto(2.0)}
Oder, für ein komplexeres Beispiel (beachten Sie: "
S::N
" würde nicht kompilieren: Bereichsauflösungsqualifizierer sind nichts, was abgeleitet werden kann):
template<class T> struct S { template<class U> struct N { N(T); N(T, U); template<class V> N(V, U); }; }; S<int>::N x{2.0, 1}; // die implizit generierten Ableitungsleitlinien sind (beachte, dass T bereits als int bekannt ist) // F1: template<class U> // S<int>::N<U> F(int); // F2: template<class U> // S<int>::N<U> F(int, U); // F3: template<class U, class V> // S<int>::N<U> F(V, U); // F4: template<class U> // S<int>::N<U> F(S<int>::N<U>); (copy deduction candidate) // Überladungsauflösung für Direct-List-Initialisierung mit "{2.0, 1}" als Initialisierer // wählt F3 mit U=int und V=double. // Der Rückgabetyp ist S<int>::N<int> // Ergebnis: // S<int>::N<int> x{2.0, 1};
Benutzerdefinierte Deduktionsanleitungen
Die Syntax eines benutzerdefinierten Deduction Guide ist die Syntax einer Funktions-(Template-)Deklaration mit nachgestelltem Rückgabetyp, mit der Ausnahme, dass sie den Namen eines Klassentemplates als Funktionsnamen verwendet:
explicit
(optional)
template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(optional)
;
|
(1) | ||||||||
template <
template-parameter-list
>
requires-clause
(optional)
explicit (optional) template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(optional)
;
|
(2) | ||||||||
| template-parameter-list | - | eine nicht-leere, kommagetrennte Liste von Template-Parametern |
| explicit | - |
ein
explicit
Specifier
|
| template-name | - | der Name des Klassentemplates, dessen Argumente abgeleitet werden sollen |
| parameter-list | - | eine (möglicherweise leere) Parameterliste |
| simple-template-id | - | eine einfache Template-Kennung |
| requires-clause | - | (seit C++20) eine requires Klausel |
|
Die Parameter von benutzerdefinierten Deduktionsanleitungen können keine Platzhaltertypen haben: Die Abbreviated Function Template -Syntax ist nicht zulässig. |
(seit C++20) |
Benutzerdefinierte Deduktionsanleitungen müssen einen Klassentemplate benennen und müssen im selben semantischen Gültigkeitsbereich des Klassentemplates eingeführt werden (was ein Namespace oder eine einschließende Klasse sein könnte) und müssen für ein Member-Klassentemplate denselben Zugriff haben, aber Deduktionsanleitungen werden nicht zu Mitgliedern dieses Gültigkeitsbereichs.
Ein Deduction Guide ist keine Funktion und hat keinen Körper. Deduction Guides werden nicht durch Namenssuche gefunden und nehmen nicht an der Überladungsauflösung teil, außer bei der Überladungsauflösung gegen andere Deduction Guides bei der Ableitung von Klassentemplate-Argumenten. Deduction Guides können nicht in derselben Übersetzungseinheit für dasselbe Klassentemplate neu deklariert werden.
// Deklaration des Templates template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; // Zusätzlicher Deduction Guide template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; // Verwendungen container c(7); // OK: leitet T=int mit einem implizit generierten Guide ab std::vector<double> v = {/* ... */}; auto d = container(v.begin(), v.end()); // OK: leitet T=double ab container e{5, 6}; // Fehler: es gibt kein std::iterator_traits<int>::value_type
Die fiktiven Konstruktoren zum Zweck der Überlagerungsauflösung (oben beschrieben) sind explizit, wenn sie einem implizit generierten Ableitungsleitfaden entsprechen, der von einem expliziten Konstruktor gebildet wird, oder einem benutzerdefinierten Ableitungsleitfaden, der als explicit deklariert ist. Wie immer werden solche Konstruktoren im Copy-Initialisierungskontext ignoriert:
template<class T> struct A { explicit A(const T&, ...) noexcept; // #1 A(T&&, ...); // #2 }; int i; A a1 = {i, i}; // Fehler: Kann nicht von Rvalue-Referenz in #2 ableiten, // und #1 ist explicit und wird bei Copy-Initialisierung nicht berücksichtigt. A a2{i, i}; // OK, #1 leitet auf A<int> ab und initialisiert ebenfalls A a3{0, i}; // OK, #2 leitet auf A<int> ab und initialisiert ebenfalls A a4 = {0, i}; // OK, #2 leitet auf A<int> ab und initialisiert ebenfalls template<class T> A(const T&, const T&) -> A<T&>; // #3 template<class T> explicit A(T&&, T&&) -> A<T>; // #4 A a5 = {0, 1}; // Fehler: #3 leitet auf A<int&> ab // und #1 & #2 führen zu gleichen Parameter-Konstruktoren. A a6{0, 1}; // OK, #4 leitet auf A<int> ab und #2 initialisiert A a7 = {0, i}; // Fehler: #3 leitet auf A<int&> ab A a8{0, i}; // Fehler: #3 leitet auf A<int&> ab // Hinweis: Prüfe https://github.com/cplusplus/CWG/issues/647, das behauptet, // dass die Beispiele a7 und a8 inkorrekt sind und möglicherweise ersetzt werden durch //A a7 = {0, i}; // Fehler: #2 und #3 passen beide, Überladungsauflösung schlägt fehl //A a8{i,i}; // Fehler: #3 leitet auf A<int&> ab, // // #1 und #2 deklarieren gleichen Konstruktor
Die Verwendung eines Member-Typedefs oder Alias-Templates in der Parameterliste eines Konstruktors oder Konstruktor-Templates macht den entsprechenden Parameter des implizit generierten Guides nicht per se zu einem nicht abgeleiteten Kontext.
template<class T> struct B { template<class U> using TA = T; template<class U> B(U, TA<U>); // #1 }; // Der implizit generierte Ableitungsleitfaden von #1 entspricht // template<class T, class U> // B(U, T) -> B<T>; // und nicht // template<class T, class U> // B(U, typename B<T>::template TA<U>) -> B<T>; // was nicht ableitbar gewesen wäre B b{(int*)0, (char*)0}; // OK, leitet B<char*> ab
Deduktion für Alias-Templates
Wenn ein Funktionsstil-Cast oder eine Variablendeklaration den Namen eines Alias-Templates
template<class T> class unique_ptr { /* ... */ }; template<class T> class unique_ptr<T[]> { /* ... */ }; template<class T> unique_ptr(T*) -> unique_ptr<T>; // #1 template<class T> unique_ptr(T*) -> unique_ptr<T[]>; // #2 template<class T> concept NonArray = !std::is_array_v<T>; template<NonArray A> using unique_ptr_nonarray = unique_ptr<A>; template<class A> using unique_ptr_array = unique_ptr<A[]>; // generierter Guide für unique_ptr_nonarray: // aus #1 (Deduktion von unique_ptr<T> aus unique_ptr<A> ergibt T = A): // template<class A> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<A>>) // auto F(A*) -> unique_ptr<A>; // aus #2 (Deduktion von unique_ptr<T[]> aus unique_ptr<A> ergibt nichts): // template<class T> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<T[]>>) // auto F(T*) -> unique_ptr<T[]>; // wobei argument_of_unique_ptr_nonarray_is_deducible_from definiert werden kann als // template<class> // class AA; // template<NonArray A> // class AA<unique_ptr_nonarray<A>> {}; // template<class T> // concept argument_of_unique_ptr_nonarray_is_deducible_from = // requires { sizeof(AA<T>); }; // generierter Guide für unique_ptr_array: // aus #1 (Deduktion von unique_ptr<T> aus unique_ptr<A[]> ergibt T = A[]): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A(*)[]) -> unique_ptr<A[]>; // aus #2 (Deduktion von unique_ptr<T[]> aus unique_ptr<A[]> ergibt T = A): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A*) -> unique_ptr<A[]>; // wobei argument_of_unique_ptr_array_is_deducible_from definiert werden kann als // template<class> // class BB; // template<class A> // class BB<unique_ptr_array<A>> {}; // template<class T> // concept argument_of_unique_ptr_array_is_deducible_from = // requires { sizeof(BB<T>); }; // Verwendung: unique_ptr_nonarray p(new int); // deduziert zu unique_ptr<int> // Deduktion-Guide generiert aus #1 gibt unique_ptr<int> zurück // Deduktion-Guide generiert aus #2 gibt unique_ptr<int[]> zurück, was ignoriert wird, weil // argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<int[]>> unerfüllt ist unique_ptr_array q(new int[42]); // deduziert zu unique_ptr<int[]> // Deduktion-Guide generiert aus #1 schlägt fehl (kann A in A(*)[] nicht aus new int[42] deduzieren) // Deduktion-Guide generiert aus #2 gibt unique_ptr<int[]> zurück |
(seit C++20) |
Hinweise
Die Ableitung von Klassentemplate-Argumenten erfolgt nur, wenn keine Template-Argumentliste vorhanden ist. Wenn eine Template-Argumentliste angegeben wird, findet keine Ableitung statt.
std::tuple t1(1, 2, 3); // OK: Ableitung std::tuple<int, int, int> t2(1, 2, 3); // OK: Alle Argumente sind angegeben std::tuple<> t3(1, 2, 3); // Fehler: Kein passender Konstruktor in tuple<>. // Keine Ableitung durchgeführt. std::tuple<int> t4(1, 2, 3); // Fehler
|
Die Argumentdeduktion von Klassentemplates für Aggregate erfordert typischerweise benutzerdefinierte Deduktionsanleitungen: template<class A, class B> struct Agg { A a; B b; }; // implicitly-generated guides are formed from default, copy, and move constructors template<class A, class B> Agg(A a, B b) -> Agg<A, B>; // ^ This deduction guide can be implicitly generated in C++20 Agg agg{1, 2.0}; // deduced to Agg<int, double> from the user-defined guide template<class... T> array(T&&... t) -> array<std::common_type_t<T...>, sizeof...(T)>; auto a = array{1, 2, 5u}; // deduced to array<unsigned, 3> from the user-defined guide |
(bis C++20) |
Benutzerdefinierte Deduktionsleitfäden müssen keine Templates sein:
template<class T> struct S { S(T); }; S(char const*) -> S<std::string>; S s{"hello"}; // abgeleitet als S<std::string>
Im Geltungsbereich eines Klassentemplates ist der Name des Templates ohne Parameterliste ein injizierter Klassenname und kann als Typ verwendet werden. In diesem Fall findet keine Klassenargumentableitung statt, und Template-Parameter müssen explizit angegeben werden:
template<class T> struct X { X(T) {} template<class Iter> X(Iter b, Iter e) {} template<class Iter> auto foo(Iter b, Iter e) { return X(b, e); // Keine Ableitung: X ist das aktuelle X<T> } template<class Iter> auto bar(Iter b, Iter e) { return X<typename Iter::value_type>(b, e); // Muss explizit angeben, was wir wollen } auto baz() { return ::X(0); // Nicht der injizierte Klassenname; abgeleitet als X<int> } };
Bei der Überlagerungsauflösung hat partielles Ordering Vorrang vor der Frage, ob eine Funktionsvorlage aus einem benutzerdefinierten Deduction Guide erzeugt wird: Wenn die aus dem Konstruktor erzeugte Funktionsvorlage spezialisierter ist als die aus dem benutzerdefinierten Deduction Guide erzeugte, wird die aus dem Konstruktor erzeugte gewählt. Da der Kopier-Deduction Candidate typischerweise spezialisierter ist als ein wrappender Konstruktor, bedeutet diese Regel, dass Kopieren generell dem Wrapping vorgezogen wird.
template<class T> struct A { A(T, int*); // #1 A(A<T>&, int*); // #2 enum { value }; }; template<class T, int N = T::value> A(T&&, int*) -> A<T>; //#3 A a{1, 0}; // verwendet #1 zur Ableitung von A<int> und Initialisierung mit #1 A b{a, 0}; // verwendet #2 (spezialisierter als #3) zur Ableitung von A<int> und Initialisierung mit #2
Wenn frühere Tiebreaker, einschließlich partieller Ordnung, versagten, zwischen zwei Kandidaten-Funktionsvorlagen zu unterscheiden, gelten die folgenden Regeln:
- Ein von einem benutzerdefinierten Ableitungsleitfaden generiertes Funktions-Template wird gegenüber einem implizit aus einem Konstruktor oder Konstruktor-Template generierten bevorzugt.
- Der Kopier-Ableitungskandidat wird gegenüber allen anderen implizit aus einem Konstruktor oder Konstruktor-Template generierten Funktions-Templates bevorzugt.
- Ein implizit aus einem Nicht-Template-Konstruktor generiertes Funktions-Template wird gegenüber einem implizit aus einem Konstruktor-Template generierten Funktions-Template bevorzugt.
template<class T> struct A { using value_type = T; A(value_type); // #1 A(const A&); // #2 A(T, T, int); // #3 template<class U> A(int, T, U); // #4 }; // #5, das Kopierdeduktionskandidat A(A); A x(1, 2, 3); // verwendet #3, generiert von einem Nicht-Template-Konstruktor template<class T> A(T) -> A<T>; // #6, weniger spezialisiert als #5 A a(42); // verwendet #6 zur Deduktion von A<int> und #1 zur Initialisierung A b = a; // verwendet #5 zur Deduktion von A<int> und #2 zur Initialisierung template<class T> A(A<T>) -> A<A<T>>; // #7, ebenso spezialisiert wie #5 A b2 = a; // verwendet #7 zur Deduktion von A<A<int>> und #1 zur Initialisierung
Eine Rvalue-Referenz auf einen cv-unqualifizierten Template-Parameter ist kein Forwarding Reference , wenn dieser Parameter ein Klassen-Template-Parameter ist:
template<class T> struct A { template<class U> A(T&&, U&&, int*); // #1: T&& ist kein Forwarding-Reference // U&& ist eine Forwarding-Reference A(T&&, int*); // #2: T&& ist kein Forwarding-Reference }; template<class T> A(T&&, int*) -> A<T>; // #3: T&& ist eine Forwarding-Reference int i, *ip; A a{i, 0, ip}; // Fehler, kann von #1 nicht ableiten A a0{0, 0, ip}; // verwendet #1 um A<int> abzuleiten und #1 zur Initialisierung A a2{i, ip}; // verwendet #3 um A<int&> abzuleiten und #2 zur Initialisierung
Wenn von einem einzelnen Argument eines Typs initialisiert wird, der eine Spezialisierung der betreffenden Klassenvorlage ist, wird das Kopierdeduzieren im Allgemeinen dem Umwickeln vorgezogen:
std::tuple t1{1}; //std::tuple<int> std::tuple t2{t1}; //std::tuple<int>, nicht std::tuple<std::tuple<int>> std::vector v1{1, 2}; // std::vector<int> std::vector v2{v1}; // std::vector<int>, nicht std::vector<std::vector<int>> (P0702R1) std::vector v3{v1, v2}; // std::vector<std::vector<int>>
Außerhalb des Sonderfalls für Kopieren vs. Umhüllen bleibt die starke Präferenz für Initialisierer-Listen-Konstruktoren in der Listeninitialisierung intakt.
std::vector v1{1, 2}; // std::vector<int> std::vector v2(v1.begin(), v1.end()); // std::vector<int> std::vector v3{v1.begin(), v1.end()}; // std::vector<std::vector<int>::iterator>
Vor der Einführung von Template-Argumentableitung für Klassen war ein gängiger Ansatz, um das explizite Angeben von Argumenten zu vermeiden, die Verwendung eines Funktions-Templates:
std::tuple p1{1, 1.0}; //std::tuple<int, double>, Verwendung von Deduktion auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, vor C++17
| Feature-Test-Makro | Wert | Std | Feature |
|---|---|---|---|
__cpp_deduction_guides
|
201703L
|
(C++17) | Template-Argument-Deduktion für Klassentemplates |
201907L
|
(C++20) | CTAD für Aggregate und Aliase |
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 2376 | C++17 |
CTAD würde auch dann durchgeführt, wenn der Typ der deklarierten Variable
sich von der Klassentemplate unterscheidet, dessen Argumente abgeleitet werden sollen |
CTAD in diesem Fall
nicht durchführen |
| CWG 2628 | C++20 | implizite Ableitungsleitlinien propagierten Constraints nicht | Constraints propagieren |
| CWG 2697 | C++20 |
es war unklar, ob die abgekürzte Funktions-Template-Syntax
in benutzerdefinierten Ableitungsleitlinien erlaubt ist |
verboten |
| CWG 2707 | C++20 | Ableitungsleitlinien konnten keine nachgestellte requires -Klausel haben | sie können |
| CWG 2714 | C++17 |
implizite Ableitungsleitlinien berücksichtigten
nicht die Standardargumente von Konstruktoren |
diese berücksichtigen |
| CWG 2913 | C++20 |
die Lösung von
CWG Issue 2707
machte die Syntax der Ableitungsleitlinie
inkonsistent mit der Funktionsdeklarationssyntax |
Syntax angepasst |
| P0702R1 | C++17 |
ein Initializer-List-Konstruktor kann den
Kopierableitungskandidaten verdrängen, was zu Wrapping führt |
Initializer-List-Phase
beim Kopieren übersprungen |