Klassenschablonen-Argumentableitung (CTAD) (seit C++17)
Um eine Klassenschablone zu instanziieren, muss jedes Schablonenargument bekannt sein, aber nicht jedes Schablonenargument muss angegeben werden. In den folgenden Kontexten leitet der Compiler die Schablonenargumente vom Typ des Initialisierers ab.
- jede Deklaration, die die Initialisierung einer Variablen und einer Variablenschablone spezifiziert, deren deklarierter Typ die Klassenschablone ist (möglicherweise cv-qualifiziert)
std::pair p(2, 4.5); // deduces to std::pair<int, double> p(2, 4.5); std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5); std::less l; // same as std::less<void> l;
template<class T> struct A { A(T, T); }; auto y = new A{1, 2}; // allocated type is A<int>
- Funktionsstil-Cast-Ausdrücke
auto lck = std::lock_guard(mtx); // deduces to std::lock_guard<std::mutex> std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); // deduces to std::back_insert_iterator<T>, // where T is the type of the container vi2 std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // deduces to Foo<T>, // where T is the unique lambda type
template<class T> struct X { constexpr X(T) {} }; template<X x> struct Y {}; Y<0> y; // OK, Y<X<int>(0)> |
(seit C++20) |
Inhalt |
[bearbeiten] Ableitung für Klassenschablonen
[bearbeiten] Implizit generierte Ableitungsführer
Wenn in einem Funktionsstil-Cast oder in der Deklaration einer Variablen der Typbezeichner ausschließlich aus dem Namen einer primären Klassenschablone C besteht (d. h. keine begleitende Schablonenargumentliste vorhanden ist), werden Kandidaten für die Ableitung wie folgt gebildet:
- Wenn
Cdefiniert ist, wird für jeden Konstruktor (oder Konstruktorschablone)Ci, der in der benannten primären Schablone deklariert ist, eine fiktive Funktion-SchabloneFikonstruiert, so dass alle folgenden Bedingungen erfüllt sind:
- Die Schablonenparameter von
Fisind die Schablonenparameter vonC, gefolgt (fallsCieine Konstruktorschablone ist) von den Schablonenparametern vonCi(Standard-Schablonenargumente sind ebenfalls enthalten).
- Die Schablonenparameter von
|
(seit C++20) |
- Die Parameterliste von
Fiist die Parameterliste vonCi. - Der Rückgabetyp von
FiistC, gefolgt von den Schablonenparametern der Klassenschablone, eingeschlossen in<>.
- Die Parameterliste von
- Wenn
Cnicht definiert ist oder keine Konstruktoren deklariert, wird eine zusätzliche fiktive Funktion-Schablone hinzugefügt, die wie oben aus einem hypothetischen KonstruktorC()abgeleitet ist.
- In jedem Fall wird eine zusätzliche fiktive Funktion-Schablone hinzugefügt, die wie oben aus einem hypothetischen Konstruktor
C(C)abgeleitet ist; diese wird als Kopierableitungskandidat bezeichnet.
- Für jeden benutzerdefinierten Ableitungsführer
Giwird eine fiktive Funktion oder Funktion-SchabloneFikonstruiert, so dass alle folgenden Bedingungen erfüllt sind:
- Die Parameterliste von
Fiist die Parameterliste vonGi. - Der Rückgabetyp von
Fiist der einfache Schablonenbezeichner vonGi. - Wenn
GiSchablonenparameter hat (Syntax (2)), istFieine Funktion-Schablone, und ihre Schablonenparameterliste ist die Schablonenparameterliste vonGi. Andernfalls istFieine 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) |
Schablonen-Argumentableitung und Überladungsauflösung wird dann für die Initialisierung eines fiktiven Objekts eines hypothetischen Klassentyps durchgeführt, dessen Konstruktorsignaturen mit den Führern übereinstimmen (außer für den Rückgabetyp), zu dem Zweck, eine Überladungssammlung zu bilden. Der Initialisierer wird durch den Kontext bereitgestellt, in dem die Klassenschablonen-Argumentableitung durchgeführt wurde, mit der Ausnahme, dass die erste Phase der Listeninitialisierung (unter Berücksichtigung von Initialisierungslistenkonstruktoren) 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 Member des hypothetischen Klassentyps. Sie sind explizit, wenn der Führer 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-Schablonenspezialisierung zur abgeleiteten Klassenschablonenspezialisierung.
template<class T> struct UniquePtr { UniquePtr(T* t); }; UniquePtr dp{new auto(2.0)}; // One declared constructor: // C1: UniquePtr(T*); // Set of implicitly-generated deduction guides: // F1: template<class T> // UniquePtr<T> F(T* p); // F2: template<class T> // UniquePtr<T> F(UniquePtr<T>); // copy deduction candidate // imaginary class to initialize: // struct X // { // template<class T> // X(T* p); // from F1 // // template<class T> // X(UniquePtr<T>); // from F2 // }; // direct-initialization of an X object // with "new double(2.0)" as the initializer // selects the constructor that corresponds to the guide F1 with T = double // For F1 with T=double, the return type is UniquePtr<double> // result: // UniquePtr<double> dp{new auto(2.0)}
Oder, für ein komplexeres Beispiel (Hinweis: "S::N" würde nicht kompilieren: Bereichsauflösungsqualifizierer sind nichts, das 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}; // the implicitly-generated deduction guides are (note that T is already known to be int) // 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) // Overload resolution for direct-list-init with "{2.0, 1}" as the initializer // chooses F3 with U=int and V=double. // The return type is S<int>::N<int> // result: // S<int>::N<int> x{2.0, 1};
[bearbeiten] Benutzerdefinierte Ableitungsführer
Die Syntax eines benutzerdefinierten Ableitungsführers ist die Syntax einer Funktions- (oder Funktionsschablonen-) Deklaration mit einem nachgestellten Rückgabetyp, außer dass sie den Namen einer Klassenschablone 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, durch Kommata getrennte Liste von Schablonenparametern |
| explicit | - | ein explicit Spezifizierer |
| template-name | - | der Name der Klassenschablone, deren Argumente abgeleitet werden sollen |
| parameter-liste | - | eine (möglicherweise leere) Parameterliste |
| simple-template-id | - | ein einfacher Schablonenbezeichner |
| requires-clause | - | (seit C++20) eine requires Klausel |
|
Die Parameter von benutzerdefinierten Ableitungsführern dürfen keine Platzhaltertypen haben: die Syntax für verkürzte Funktion-Schablonen ist nicht erlaubt. |
(seit C++20) |
Benutzerdefinierte Ableitungsführer müssen eine Klassenschablone benennen und müssen innerhalb desselben semantischen Gültigkeitsbereichs der Klassenschablone (was ein Namespace oder eine einschließende Klasse sein kann) eingeführt werden. Für eine Member-Klassenschablone müssen sie denselben Zugriff haben, aber Ableitungsführer werden keine Member dieses Gültigkeitsbereichs.
Ein Ableitungsführer ist keine Funktion und hat keinen Körper. Ableitungsführer werden nicht durch Namenssuche gefunden und nehmen nicht an der Überladungsauflösung teil, außer für die Überladungsauflösung mit anderen Ableitungsführern bei der Ableitung von Klassenschablonenargumenten. Ableitungsführer können nicht in derselben Übersetzungseinheit für dieselbe Klassenschablone neu deklariert werden.
// declaration of the template template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; // additional deduction guide template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; // uses container c(7); // OK: deduces T=int using an implicitly-generated guide std::vector<double> v = {/* ... */}; auto d = container(v.begin(), v.end()); // OK: deduces T=double container e{5, 6}; // Error: there is no std::iterator_traits<int>::value_type
Die fiktiven Konstruktoren für den Zweck der Überladungsauflösung (wie oben beschrieben) sind explizit, wenn sie einem implizit generierten Ableitungsführer entsprechen, der aus einem expliziten Konstruktor gebildet wurde, oder einem benutzerdefinierten Ableitungsführer, der explizit deklariert wurde. Wie immer werden solche Konstruktoren im Kopierinitialisierungskontext ignoriert.
template<class T> struct A { explicit A(const T&, ...) noexcept; // #1 A(T&&, ...); // #2 }; int i; A a1 = {i, i}; // error: cannot deduce from rvalue reference in #2, // and #1 is explicit, and not considered in copy-initialization. A a2{i, i}; // OK, #1 deduces to A<int> and also initializes A a3{0, i}; // OK, #2 deduces to A<int> and also initializes A a4 = {0, i}; // OK, #2 deduces to A<int> and also initializes 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}; // error: #3 deduces to A<int&> // and #1 & #2 result in same parameter constructors. A a6{0, 1}; // OK, #4 deduces to A<int> and #2 initializes A a7 = {0, i}; // error: #3 deduces to A<int&> A a8{0, i}; // error: #3 deduces to A<int&> // Note: check https://github.com/cplusplus/CWG/issues/647, claiming that // examples a7 and a8 are incorrect, to be possibly replaced as //A a7 = {0, i}; // error: #2 and #3 both match, overload resolution fails //A a8{i,i}; // error: #3 deduces to A<int&>, // // #1 and #2 declare same constructor
Die Verwendung eines Member-Typedefs oder einer Alias-Schablone in der Parameterliste eines Konstruktors oder einer Konstruktorschablone macht den entsprechenden Parameter des implizit generierten Führers nicht von sich aus zu einem nicht-abgeleiteten Kontext.
template<class T> struct B { template<class U> using TA = T; template<class U> B(U, TA<U>); // #1 }; // Implicit deduction guide generated from #1 is the equivalent of // template<class T, class U> // B(U, T) -> B<T>; // rather than // template<class T, class U> // B(U, typename B<T>::template TA<U>) -> B<T>; // which would not have been deducible B b{(int*)0, (char*)0}; // OK, deduces B<char*>
Ableitung für Alias-SchablonenWenn ein Funktionsstil-Cast oder eine Variablendeklaration den Namen einer Alias-Schablone
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[]>; // generated guide for unique_ptr_nonarray: // from #1 (deduction of unique_ptr<T> from unique_ptr<A> yields T = A): // template<class A> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<A>>) // auto F(A*) -> unique_ptr<A>; // from #2 (deduction of unique_ptr<T[]> from unique_ptr<A> yields nothing): // template<class T> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<T[]>>) // auto F(T*) -> unique_ptr<T[]>; // where argument_of_unique_ptr_nonarray_is_deducible_from can be defined as // 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>); }; // generated guide for unique_ptr_array: // from #1 (deduction of unique_ptr<T> from unique_ptr<A[]> yields T = A[]): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A(*)[]) -> unique_ptr<A[]>; // from #2 (deduction of unique_ptr<T[]> from unique_ptr<A[]> yields T = A): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A*) -> unique_ptr<A[]>; // where argument_of_unique_ptr_array_is_deducible_from can be defined as // 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>); }; // Use: unique_ptr_nonarray p(new int); // deduced to unique_ptr<int> // deduction guide generated from #1 returns unique_ptr<int> // deduction guide generated from #2 returns unique_ptr<int[]>, which is ignored because // argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<int[]>> is unsatisfied unique_ptr_array q(new int[42]); // deduced to unique_ptr<int[]> // deduction guide generated from #1 fails (cannot deduce A in A(*)[] from new int[42]) // deduction guide generated from #2 returns unique_ptr<int[]> |
(seit C++20) |
[bearbeiten] Anmerkungen
Die Klassenschablonen-Argumentableitung wird nur dann durchgeführt, wenn keine Schablonenargumentliste vorhanden ist. Wenn eine Schablonenargumentliste angegeben ist, findet keine Ableitung statt.
std::tuple t1(1, 2, 3); // OK: deduction std::tuple<int, int, int> t2(1, 2, 3); // OK: all arguments are provided std::tuple<> t3(1, 2, 3); // Error: no matching constructor in tuple<>. // No deduction performed. std::tuple<int> t4(1, 2, 3); // Error
|
Die Klassenschablonen-Argumentableitung von Aggregaten erfordert typischerweise benutzerdefinierte Ableitungsführer. 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 Ableitungsführer müssen keine Schablonen sein.
template<class T> struct S { S(T); }; S(char const*) -> S<std::string>; S s{"hello"}; // deduced to S<std::string>
Innerhalb des Gültigkeitsbereichs einer Klassenschablone ist der Name der Schablone ohne Parameterliste ein injizierter Klassenname und kann als Typ verwendet werden. In diesem Fall findet keine Klassenschablonen-Argumentableitung statt und Schablonenparameter 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); // no deduction: X is the current X<T> } template<class Iter> auto bar(Iter b, Iter e) { return X<typename Iter::value_type>(b, e); // must specify what we want } auto baz() { return ::X(0); // not the injected-class-name; deduced to be X<int> } };
Bei der Überladungsauflösung hat die Teilordnung Vorrang vor der Frage, ob eine Funktion-Schablone aus einem benutzerdefinierten Ableitungsführer generiert wurde: Wenn die von einem Konstruktor generierte Funktion-Schablone spezialisierter ist als die vom benutzerdefinierten Ableitungsführer generierte, wird die vom Konstruktor generierte gewählt. Da der Kopierableitungskandidat typischerweise spezialisierter ist als ein umhüllender Konstruktor, bedeutet diese Regel, dass das Kopieren im Allgemeinen dem Umhüllen 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}; // uses #1 to deduce A<int> and initializes with #1 A b{a, 0}; // uses #2 (more specialized than #3) to deduce A<int> and initializes with #2
Wenn frühere Auswahlkriterien, einschließlich Teilordnung, zwei Kandidaten-Funktionsschablonen nicht unterscheiden konnten, gelten die folgenden Regeln:
- Eine von einem benutzerdefinierten Ableitungsführer generierte Funktion-Schablone wird gegenüber einer implizit aus einem Konstruktor oder einer Konstruktorschablone generierten bevorzugt.
- Der Kopierableitungskandidat wird allen anderen implizit aus einem Konstruktor oder einer Konstruktorschablone generierten Funktion-Schablonen vorgezogen.
- Eine implizit aus einem Nicht-Schablonenkonstruktor generierte Funktion-Schablone wird gegenüber einer implizit aus einer Konstruktorschablone generierten Funktion-Schablone 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, the copy deduction candidate A(A); A x(1, 2, 3); // uses #3, generated from a non-template constructor template<class T> A(T) -> A<T>; // #6, less specialized than #5 A a(42); // uses #6 to deduce A<int> and #1 to initialize A b = a; // uses #5 to deduce A<int> and #2 to initialize template<class T> A(A<T>) -> A<A<T>>; // #7, as specialized as #5 A b2 = a; // uses #7 to deduce A<A<int>> and #1 to initialize
Eine rvalue-Referenz auf einen cv-unqualifizierten Schablonenparameter ist keine weiterleitende Referenz, wenn dieser Parameter ein Klassenschablonenparameter ist.
template<class T> struct A { template<class U> A(T&&, U&&, int*); // #1: T&& is not a forwarding reference // U&& is a forwarding reference A(T&&, int*); // #2: T&& is not a forwarding reference }; template<class T> A(T&&, int*) -> A<T>; // #3: T&& is a forwarding reference int i, *ip; A a{i, 0, ip}; // error, cannot deduce from #1 A a0{0, 0, ip}; // uses #1 to deduce A<int> and #1 to initialize A a2{i, ip}; // uses #3 to deduce A<int&> and #2 to initialize
Bei der Initialisierung aus einem einzelnen Argument eines Typs, der eine Spezialisierung der betreffenden Klassenschablone ist, wird die Kopierableitung standardmäßig im Allgemeinen der Umhüllung vorgezogen.
std::tuple t1{1}; //std::tuple<int> std::tuple t2{t1}; //std::tuple<int>, not std::tuple<std::tuple<int>> std::vector v1{1, 2}; // std::vector<int> std::vector v2{v1}; // std::vector<int>, not 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 Bevorzugung von Initialisierungslistenkonstruktoren bei der Listeninitialisierung bestehen.
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>
Bevor die Klassenschablonen-Argumentableitung eingeführt wurde, war die Verwendung einer Funktion-Schablone ein gängiger Ansatz, um die explizite Angabe von Argumenten zu vermeiden.
std::tuple p1{1, 1.0}; //std::tuple<int, double>, using deduction auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, pre-C++17
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_deduction_guides |
201703L |
(C++17) | Schablonen-Argumentableitung für Klassenschablonen |
201907L |
(C++20) | CTAD für Aggregate und Aliase |
[bearbeiten] 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 Variablen sich von der Klassenschablone unterscheidet, deren Argumente abgeleitet werden sollen |
führt keine durch CTAD in diesem Fall durch |
| CWG 2628 | C++20 | implizite Ableitungsführer propagierten keine Constraints | propagiert Constraints |
| CWG 2697 | C++20 | war unklar, ob die verkürzte Funktion-Schablonen- Syntax in benutzerdefinierten Ableitungsführern erlaubt ist |
verboten |
| CWG 2707 | C++20 | Ableitungsführer konnten keine nachgestellte requires Klausel haben | sie können es |
| CWG 2714 | C++17 | implizite Ableitungsführer berücksichtigten die Standardargumente von Konstruktoren nicht |
berücksichtigt sie |
| CWG 2913 | C++20 | die Auflösung von CWG-Problem 2707 machte die Syntax von Ableitungsführern inkonsistent mit der Syntax von Funktionsdeklarationen |
passt die Syntax an |
| P0702R1 | C++17 | ein Initialisierungslistenkonstruktor kann den Kopierableitungskandidaten übertrumpfen, was zu Umhüllung führt |
Initialisierungslistenphase wird beim Kopieren übersprungen |