Namespaces
Variants

Template argument deduction

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Um eine function template zu instanziieren, müssen alle Template-Argumente bekannt sein, aber nicht jedes Template-Argument muss angegeben werden. Wenn möglich, leitet der Compiler die fehlenden Template-Argumente aus den Funktionsargumenten ab. Dies geschieht, wenn ein Funktionsaufruf versucht wird, wenn die Adresse einer Funktionsvorlage genommen wird und in einigen other contexts :

template<typename To, typename From>
To convert(From f);
void g(double d)
{
    int i = convert<int>(d);    // ruft convert<int, double>(double) auf
    char c = convert<char>(d);  // ruft convert<char, double>(double) auf
    int(*ptr)(float) = convert; // instanziiert convert<int, float>(float)
                                // und speichert seine Adresse in ptr
}

Dieser Mechanismus ermöglicht die Verwendung von Template-Operatoren, da es keine Syntax gibt, um Template-Argumente für einen Operator anzugeben, außer durch Umschreiben als Funktionsaufrufausdruck:

#include <iostream>
int main()
{
    std::cout << "Hallo, Welt" << std::endl;
    // operator<< wird über ADL als std::operator<< nachgeschlagen,
    // dann beide Male zu operator<<<char, std::char_traits<char>> abgeleitet
    // std::endl wird zu &std::endl<char, std::char_traits<char>> abgeleitet
}

Die Template-Argument-Deduktion findet statt nach der Namenssuche für Funktions-Templates (die eine argumentabhängige Suche beinhalten kann) und vor der Template-Argument-Substitution (die SFINAE beinhalten kann) und der Überladungsauflösung .

Template-Argument-Deduktion wird auch durchgeführt, wenn der Name eines Klassentemplates als Typ eines zu konstruierenden Objekts verwendet wird:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);
std::copy_n(vi1, 3, std::back_insert_iterator(vi2));
std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...}));
auto lck = std::lock_guard(foo.mtx);
std::lock_guard lck2(foo.mtx, ul);

Template-Argument-Deduktion für Klassentemplates findet in Deklarationen und in expliziten Cast-Ausdrücken statt; siehe Klassentemplate-Argument-Deduktion für Details.

(seit C++17)

Inhaltsverzeichnis

Ableitung von einem Funktionsaufruf

Template-Argument-Deduktion versucht, Template-Argumente zu bestimmen (Typen für Typ-Template-Parameter T , Templates für Template-Template-Parameter TT und Werte für konstante Template-Parameter I ), die in jeden Parameter P eingesetzt werden können, um den deduzierten Typ A zu erzeugen, der nach den unten aufgeführten Anpassungen dem Typ des Arguments A entspricht.

Wenn mehrere Parameter vorhanden sind, wird jedes P / A -Paar separat abgeleitet und die abgeleiteten Template-Argumente werden anschließend kombiniert. Wenn die Ableitung für ein P / A -Paar fehlschlägt oder mehrdeutig ist, oder wenn verschiedene Paare unterschiedliche abgeleitete Template-Argumente ergeben, oder wenn ein Template-Argument weder abgeleitet noch explizit angegeben wird, schlägt die Kompilierung fehl.

Wenn das Entfernen von Referenzen und CV-Qualifizierern von P zu std:: initializer_list < P '> führt und A eine braced-init-list ist, wird die Deduktion für jedes Element der Initialisierungsliste durchgeführt, wobei P' als Parameter und das Listenelement A' als Argument verwendet wird:

template<class T>
void f(std::initializer_list<T>);
f({1, 2, 3});  // P = std::initializer_list<T>, A = {1, 2, 3}
               // P'1 = T, A'1 = 1: deduced T = int
               // P'2 = T, A'2 = 2: deduced T = int
               // P'3 = T, A'3 = 3: deduced T = int
               // OK: deduced T = int
f({1, "abc"}); // P = std::initializer_list<T>, A = {1, "abc"}
               // P'1 = T, A'1 = 1: deduced T = int
               // P'2 = T, A'2 = "abc": deduced T = const char*
               // error: deduction fails, T is ambiguous

Wenn das Entfernen von Referenzen und CV-Qualifizierern von P zu P' [ N ] führt und A eine nicht-leere braced-init-list ist, wird die Deduktion wie oben durchgeführt, außer wenn N ein konstanter Template-Parameter ist, wird dieser aus der Länge der Initialisierungsliste deduziert:

template<class T, int N>
void h(T const(&)[N]);
h({1, 2, 3}); // deduced T = int, deduced N = 3
template<class T>
void j(T const(&)[3]);
j({42}); // deduced T = int, array bound is not a parameter, not considered
struct Aggr
{
    int i;
    int j;
};
template<int N>
void k(Aggr const(&)[N]);
k({1, 2, 3});       // error: deduction fails, no conversion from int to Aggr
k({{1}, {2}, {3}}); // OK: deduced N = 3
template<int M, int N>
void m(int const(&)[M][N]);
m({{1, 2}, {3, 4}}); // deduced M = 2, deduced N = 2
template<class T, int N>
void n(T const(&)[N], T);
n({{1}, {2}, {3}}, Aggr()); // deduced T = Aggr, deduced N = 3

Wenn ein parameter pack als letztes P erscheint, wird der Typ P mit dem Typ A jedes verbleibenden Arguments des Aufrufs abgeglichen. Jeder Abgleich deduziert die Template-Argumente für die nächste Position in der Pack-Expansion:

template<class... Types>
void f(Types&...);
void h(int x, float& y)
{
    const int z = x;
    f(x, y, z); // P = Types&..., A1 = x: deduced first member of Types... = int
                // P = Types&..., A2 = y: deduced second member of Types... = float
                // P = Types&..., A3 = z: deduced third member of Types... = const int
                // calls f<int, float, const int>
}


(seit C++11)

Wenn P ein Funktionstyp, Zeiger auf Funktionstyp oder Zeiger auf Memberfunktionstyp ist und wenn A ein Satz überladener Funktionen ist, der keine Funktions-Templates enthält, wird Template-Argument-Deduktion mit jeder Überladung versucht. Wenn nur eine erfolgreich ist, wird diese erfolgreiche Deduktion verwendet. Wenn keine oder mehr als eine erfolgreich ist, ist der Template-Parameter ein nicht-deduzierter Kontext (siehe unten):

template<class T>
int f(T(*p)(T));
int g(int);
int g(char);
f(g); // P = T(*)(T), A = Überladungsmenge
      // P = T(*)(T), A1 = int(int): T = int abgeleitet
      // P = T(*)(T), A2 = int(char): T-Ableitung fehlgeschlagen
      // nur eine Überladung funktioniert, Ableitung erfolgreich

Vor Beginn der Abzugsoperation werden die folgenden Anpassungen an P und A vorgenommen:

1) Wenn P kein Referenztyp ist,
a) wenn A ein Array-Typ ist, A wird durch den Pointer-Typ ersetzt, der durch Array-zu-Pointer-Konvertierung erhalten wird;
b) andernfalls, falls A ein Funktionstyp ist, A wird durch den Zeigertyp ersetzt, der durch die Funktion-zu-Zeiger-Konvertierung erhalten wird;
c) andernfalls, wenn A ein cv-qualifizierter Typ ist, werden die obersten CV-Qualifizierer für die Deduktion ignoriert:
template<class T>
void f(T);
int a[3];
f(a); // P = T, A = int[3], adjusted to int*: deduced T = int*
void b(int);
f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int)
const int c = 13;
f(c); // P = T, A = const int, adjusted to int: deduced T = int
2) Wenn P ein cv-qualifizierter Typ ist, werden die obersten CV-Qualifizierer für die Deduktion ignoriert.
3) Wenn P ein Referenztyp ist, wird der referenzierte Typ für die Deduktion verwendet.
4) Wenn P eine Rvalue-Referenz auf einen cv-unqualifizierten Template-Parameter ist (sogenannte Forwarding References ) und das entsprechende Funktionsargument ein Lvalue ist, wird der Typ Lvalue-Referenz auf A anstelle von A für die Deduktion verwendet (Hinweis: Dies ist die Grundlage für die Wirkungsweise von std::forward . Hinweis: Bei der Class Template Argument Deduction ist der Template-Parameter einer Klassenvorlage niemals eine Forwarding Reference (seit C++17) ):
template<class T>
int f(T&&);       // P is an rvalue reference to cv-unqualified T (forwarding reference)
template<class T>
int g(const T&&); // P is an rvalue reference to cv-qualified T (not special)
int main()
{
    int i;
    int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case)
    int n2 = f(0); // argument is not lvalue: calls f<int>(int&&)
//  int n3 = g(i); // error: deduces to g<int>(const int&&), which
                   // cannot bind an rvalue reference to an lvalue
}

Nach diesen Transformationen läuft der Deduktionsprozess wie unten beschrieben ab (vgl. Abschnitt Deduktion von einem Typ ) und versucht, solche Template-Argumente zu finden, die das deduzierte A (d.h. P nach den oben aufgeführten Anpassungen und der Substitution der deduzierten Template-Parameter) identisch zum transformierten A machen würden, also A nach den oben aufgeführten Anpassungen.

Falls die übliche Deduktion von P und A fehlschlägt, werden folgende Alternativen zusätzlich in Betracht gezogen:

1) Wenn P ein Referenztyp ist, kann das abgeleitete A (d.h. der Typ, auf den die Referenz verweist) stärker cv-qualifiziert sein als das transformierte A :
template<typename T>
void f(const T& t);
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
      // deduced T = bool, deduced A = const bool
      // deduced A is more cv-qualified than A
2) Der transformierte A kann ein anderer Zeiger oder Zeiger auf einen Membertyp sein, der über eine Qualifikationskonvertierung oder eine Funktionszeigerkonvertierung (seit C++17) in den abgeleiteten A konvertiert werden kann:
template<typename T>
void f(const T*);
int* p;
f(p); // P = const T*, A = int*:
      // deduced T = int, deduced A = const int*
      // qualification conversion applies (from int* to const int*)
3) Wenn P eine Klasse ist und P die Form simple-template-id hat, dann kann das transformierte A eine abgeleitete Klasse des abgeleiteten A sein. Ebenso, wenn P ein Zeiger auf eine Klasse der Form simple-template-id ist, kann das transformierte A ein Zeiger auf eine abgeleitete Klasse sein, auf die der abgeleitete A zeigt:
template<class T>
struct B {};
template<class T>
struct D : public B<T> {};
template<class T>
void f(B<T>&) {}
void f()
{
    D<int> d;
    f(d); // P = B<T>&, adjusted to P = B<T> (a simple-template-id), A = D<int>:
          // deduced T = int, deduced A = B<int>
          // A is derived from deduced A
}

Nicht abgeleitete Kontexte

In den folgenden Fällen nehmen die Typen, Templates und Konstanten, die zur Zusammensetzung von P verwendet werden, nicht an der Template-Argumentableitung teil, sondern verwenden die Template-Argumente, die entweder anderweitig abgeleitet oder explizit angegeben wurden. Wenn ein Template-Parameter nur in nicht abgeleiteten Kontexten verwendet wird und nicht explizit angegeben ist, schlägt die Template-Argumentableitung fehl.

1) Der nested-name-specifier (alles links des Bereichsauflösungsoperators :: ) eines Typs, der mittels einer qualified-id spezifiziert wurde:
// the identity template, often used to exclude specific arguments from deduction
// (available as std::type_identity as of C++20)
template<typename T>
struct identity { typedef T type; };
template<typename T>
void bad(std::vector<T> x, T value = 1);
template<typename T>
void good(std::vector<T> x, typename identity<T>::type value = 1);
std::vector<std::complex<double>> x;
bad(x, 1.2);  // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
              // P1/A1: deduced T = std::complex<double>
              // P2 = T, A2 = double
              // P2/A2: deduced T = double
              // error: deduction fails, T is ambiguous
good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
              // P1/A1: deduced T = std::complex<double>
              // P2 = identity<T>::type, A2 = double
              // P2/A2: uses T deduced by P1/A1 because T is to the left of :: in P2
              // OK: T = std::complex<double>
2) Ein Pack-Indexierungs-Spezifizierer oder ein Pack-Indexierungs-Ausdruck :
template<typename... Ts>
void f(Ts...[0], std::tuple<Ts...>);
f(3, std::tuple(5, 'A'));
// P2 = std::tuple<Ts...>, A2 = std::tuple<int, char>
// P2/A2: deduced first member of Ts... = int
// P2/A2: deduced second member of Ts... = char
// P1 = Ts...[0], A1 = int: Ts...[0] is in non-deduced context
(seit C++26)
3) Der Ausdruck eines decltype -Spezifizierers:
template<typename T>
void f(decltype(*std::declval<T>()) arg);
int n;
f<int*>(n); // P = decltype(*declval<T>()), A = int: T is in non-deduced context
(seit C++11)
4) Ein konstanter Template-Argument oder eine Array-Grenze, in der ein Teilausdruck auf einen Template-Parameter verweist:
template<std::size_t N>
void f(std::array<int, 2 * N> a);
std::array<int, 10> a;
f(a); // P = std::array<int, 2 * N>, A = std::array<int, 10>:
      // 2 * N is non-deduced context, N cannot be deduced
      // note: f(std::array<int, N> a) would be able to deduce N
5) Ein Template-Parameter, der im Parametertyp eines Funktionsparameters verwendet wird, der ein Standardargument hat, das im Aufruf verwendet wird, für den Argumentdeduktion durchgeführt wird:
template<typename T, typename F>
void f(const std::vector<T>& v, const F& comp = std::less<T>());
std::vector<std::string> v(3);
f(v); // P1 = const std::vector<T>&, A1 = std::vector<std::string> lvalue
      // P1/A1 deduced T = std::string
      // P2 = const F&, A2 = std::less<std::string> rvalue
      // P2 is non-deduced context for F (template parameter) used in the
      // parameter type (const F&) of the function parameter comp,
      // that has a default argument that is being used in the call f(v)
6) Der Parameter P , dessen A eine Funktion oder eine Menge von Überladungen ist, sodass mehr als eine Funktion zu P passt oder keine Funktion zu P passt oder die Menge von Überladungen eine oder mehrere Funktionsvorlagen enthält:
template<typename T>
void out(const T& value) { std::cout << value; }
out("123");     // P = const T&, A = const char[4] lvalue: deduced T = char[4]
out(std::endl); // P = const T&, A = function template: T is in non-deduced context
7) Der Parameter P , dessen A eine Brace-Initialisierungsliste ist, aber P ist nicht std::initializer_list , eine Referenz darauf (möglicherweise cv-qualifiziert) oder eine Referenz auf ein Array}}:
template<class T>
void g1(std::vector<T>);
template<class T>
void g2(std::vector<T>, T x);
g1({1, 2, 3});     // P = std::vector<T>, A = {1, 2, 3}: T ist im nicht-deduzierten Kontext
                   // Fehler: T ist nicht explizit angegeben oder von einem anderen P/A deduziert
g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T ist im nicht-deduzierten Kontext
                   // P2 = T, A2 = int: deduziert T = int
8) Der Parameter P , welcher ein Parameterpaket ist und nicht am Ende der Parameterliste steht:
template<class... Ts, class T>
void f1(T n, Ts... args);
template<class... Ts, class T>
void f2(Ts... args, T n);
f1(1, 2, 3, 4); // P1 = T, A1 = 1: deduced T = int
                // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: deduced Ts = [int, int, int]
f2(1, 2, 3, 4); // P1 = Ts...: Ts is non-deduced context
9) Die Template-Parameterliste, die innerhalb des Parameters P erscheint und eine Paketentwicklung enthält, die nicht ganz am Ende der Template-Parameterliste steht:
template<int...>
struct T {};
template<int... Ts1, int N, int... Ts2>
void good(const T<N, Ts1...>& arg1, const T<N, Ts2...>&);
template<int... Ts1, int N, int... Ts2>
void bad(const T<Ts1..., N>& arg1, const T<Ts2..., N>&);
T<1, 2> t1;
T<1, -1, 0> t2;
good(t1, t2); // P1 = const T<N, Ts1...>&, A1 = T<1, 2>:
              // deduced N = 1, deduced Ts1 = [2]
              // P2 = const T<N, Ts2...>&, A2 = T<1, -1, 0>:
              // deduced N = 1, deduced Ts2 = [-1, 0]
bad(t1, t2);  // P1 = const T<Ts1..., N>&, A1 = T<1, 2>:
              // <Ts1..., N> is non-deduced context
              // P2 = const T<Ts2..., N>&, A2 = T<1, -1, 0>:
              // <Ts2..., N> is non-deduced context
(seit C++11)
10) Für P vom Array-Typ (aber nicht Referenz auf Array oder Zeiger auf Array), die Haupt-Array-Grenze:
template<int i>
void f1(int a[10][i]);
template<int i>
void f2(int a[i][20]);    // P = int[i][20], array type
template<int i>
void f3(int (&a)[i][20]); // P = int(&)[i][20], reference to array
void g()
{
    int a[10][20];
    f1(a);     // OK: deduced i = 20
    f1<20>(a); // OK
    f2(a);     // error: i is non-deduced context
    f2<10>(a); // OK
    f3(a);     // OK: deduced i = 10
    f3<10>(a); // OK
}

In jedem Fall, wenn irgendein Teil eines Typnamens nicht abgeleitet wird, ist der gesamte Typname ein nicht abgeleiteter Kontext. Jedoch können zusammengesetzte Typen sowohl abgeleitete als auch nicht abgeleitete Typnamen enthalten. Zum Beispiel, in A < T > :: B < T2 > , T ist nicht abgeleitet aufgrund von Regel #1 (geschachtelter Namensspezifizierer), und T2 ist nicht abgeleitet, weil es Teil desselben Typnamens ist, aber in void ( * f ) ( typename A < T > :: B , A < T > ) , das T in A < T > :: B ist nicht abgeleitet (aufgrund derselben Regel), während das T in A < T > abgeleitet wird.

Ableitung von einem Typ

Gegeben einen Funktionsparameter P , der von einem oder mehreren Typ-Template-Parametern T i, Template-Template-Parametern TT i oder konstanten Template-Parametern I i abhängt, und dem entsprechenden Argument A , findet Deduktion statt, wenn P eine der folgenden Formen hat:

  • cv (optional) T ;
  • T* ;
  • T& ;
  • T&& ;
(seit C++11)
  • T (optional) [ I (optional) ] ;
  • T (optional) ( U (optional) ) ;
(bis C++17)
  • T (optional) ( U (optional) ) noexcept( I (optional) ) ;
(seit C++17)
  • T (optional) U (optional) ::* ;
  • TT (optional) <T> ;
  • TT (optional) <I> ;
  • TT (optional) <TU> ;
  • TT (optional) <> .

In den obigen Formen,

  • T (optional) oder U (optional) repräsentiert einen Typ oder eine parameter-type-list , die entweder diese Regeln rekursiv erfüllt, ein nicht abgeleiteter Kontext in P oder A ist, oder der gleiche nicht-abhängige Typ in P und A ist.
  • TT (optional) oder TU (optional) repräsentiert entweder eine Klassentemplate oder einen Template-Template-Parameter.
  • I (optional) repräsentiert einen Ausdruck, der entweder ein I ist, wertabhängig in P oder A ist, oder den gleichen konstanten Wert in P und A hat.
  • noexcept( I (optional) ) stellt eine Ausnahmespezifikation dar, in der der möglicherweise implizite noexcept -Spezifizierer-Operand die Regeln für ein I (optional) oben erfüllt.
(seit C++17)

Wenn P eine der Formen hat, die eine Template-Parameterliste <T> oder <I> einschließt, dann wird jedes Element P i dieser Template-Argumentliste mit dem entsprechenden Template-Argument A i seines A abgeglichen. Wenn das letzte P i eine Pack-Expansion ist, dann wird sein Muster mit jedem verbleibenden Argument in der Template-Argumentliste von A verglichen. Ein nachfolgender Parameter-Pack, der nicht anderweitig deduziert wird, wird zu einem leeren Parameter-Pack deduziert.

Wenn P eine der Formen hat, die eine Funktionsparameterliste (T) einschließt, dann wird jeder Parameter P i aus dieser Liste mit dem entsprechenden Argument A i aus der Funktionsparameterliste von A verglichen. Wenn der letzte P i eine Pack-Expansion ist, dann wird sein Deklarator mit jedem verbleibenden A i in der Parametertypenliste von A verglichen.

Formulare können geschachtelt und rekursiv verarbeitet werden:

  • X < int > ( * ) ( char [ 6 ] ) ist ein Beispiel für T* , wobei T gleich X < int > ( char [ 6 ] ) ist;
  • X < int > ( char [ 6 ] ) ist ein Beispiel für T (optional) ( U (optional) ) , wobei T für X < int > und U für char [ 6 ] steht;
(bis C++17)
  • X < int > ( char [ 6 ] ) ist ein Beispiel für T (optional) ( U (optional) ) noexcept( I (optional) ) , wobei T für X < int > , U für char [ 6 ] und I in der impliziten noexcept -Spezifikation für false steht;
(seit C++17)
  • X < int > ist ein Beispiel für TT (optional) <T> , wobei TT X und T int ist, und
  • char [ 6 ] ist ein Beispiel für T (optional) [ I (optional) ] , wobei T char und I std:: size_t ( 6 ) ist.

Typ-Template-Argument kann nicht vom Typ eines konstanten Template-Arguments abgeleitet werden:

template<typename T, T i>
void f(double a[10][i]);
double v[10][20];
f(v); // P = double[10][i], A = double[10][20]:
      // i kann zu 20 abgeleitet werden
      // aber T kann nicht vom Typ von i abgeleitet werden
(bis C++17)

Wenn der Wert des Arguments, das einem konstanten Template-Parameter P mit abhängigem Typ entspricht, aus einem Ausdruck abgeleitet wird, werden die Template-Parameter im Typ von P aus dem Typ des Werts abgeleitet.

template<long n>
struct A {};
template<class T>
struct C;
template<class T, T n>
struct C<A<n>> { using Q = T; };
typedef long R;
typedef C<A<2>>::Q R; // OK: T wurde zu long abgeleitet
                      // aus dem Template-Argumentwert im Typ A<2>
template<auto X>
class bar {};
template<class T, T n>
void f(bar<n> x);
f(bar<3>{}); // OK: T wurde zu int abgeleitet (und n zu 3)
             // aus dem Template-Argumentwert im Typ bar<3>

Der Typ von N im Typ T[N] ist std::size_t .

template<class T, T i>
void f(int (&a)[i]);
int v[10];
f(v); // OK: T ist std::size_t

Der Typ von B in noexcept ( B ) -Spezifizierer eines Funktionstyps ist bool .

template<bool>
struct A {};
template<auto>
struct B;
template<auto X, void (*F)() noexcept(X)>
struct B<F> { A<X> ax; };
void f_nothrow() noexcept;
B<f_nothrow> bn; // OK: X wird als true abgeleitet und der Typ von X wird als bool abgeleitet.
(seit C++17)

Wenn ein konstanter Template-Parameter einer Funktionsvorlage in der Template-Parameterliste eines Funktionsparameters (der ebenfalls ein Template ist) verwendet wird und das entsprechende Template-Argument abgeleitet wird, muss der Typ des abgeleiteten Template-Arguments (wie in seiner umschließenden Template-Parameterliste angegeben, was bedeutet, dass Referenzen erhalten bleiben) exakt mit dem Typ des konstanten Template-Parameters übereinstimmen, außer dass CV-Qualifizierer entfernt werden, und außer wenn das Template-Argument von einer Array-Grenze abgeleitet wird – in diesem Fall ist jeder integrale Typ erlaubt, selbst bool obwohl es immer zu true werden würde:

template<int i>
class A {};
template<short s>
void f(A<s>); // der Typ des konstanten Template-Parameters ist short
void k1()
{
    A<1> a;  // der Typ des konstanten Template-Parameters von a ist int
    f(a);    // P = A<(short)s>, A = A<(int)1>
             // Fehler: abgeleiteter konstanter Template-Argument hat nicht denselben
             // Typ wie sein entsprechendes Template-Argument
    f<1>(a); // OK: das Template-Argument wird nicht abgeleitet,
             // dies ruft f<(short)1>(A<(short)1>) auf
}
template<int&>
struct X;
template<int& R>
void k2(X<R>&);
int n;
void g(X<n> &x)
{
    k2(x); // P = X<R>, A = X<n>
           // Parametertyp ist int&
           // Argumenttyp ist int& in der Template-Deklaration von struct X
           // OK (mit CWG 2091): leitet R ab, um auf n zu referenzieren
}

Typ-Template-Parameter kann nicht vom Typ eines Funktionsstandardarguments abgeleitet werden:

template<typename T>
void f(T = 5, T = 7);
void g()
{
    f(1);     // OK: ruft f<int>(1, 7) auf
    f();      // Fehler: T kann nicht abgeleitet werden
    f<int>(); // OK: ruft f<int>(5, 7) auf
}

Die Ableitung von Template-Template-Parametern kann den Typ verwenden, der in der Template-Spezialisierung verwendet wird, die im Funktionsaufruf verwendet wird:

template<template<typename> class X>
struct A {}; // A ist eine Template mit einem TT-Parameter
template<template<typename> class TT>
void f(A<TT>) {}
template<class T>
struct B {};
A<B> ab;
f(ab); // P = A<TT>, A = A<B>: abgeleitet TT = B, ruft f(A<B>) auf

Andere Kontexte

Neben Funktionsaufrufen und Operatorausdrücken wird die Template-Argument-Deduktion in den folgenden Situationen verwendet:

Automatische Typableitung

Die Template-Argumentableitung wird in Deklarationen von Variablen verwendet, wenn die Bedeutung des auto-Spezifizierers aus dem Initialisierer der Variable abgeleitet wird.

Der Parameter P wird wie folgt erhalten: in T , dem deklarierten Typ der Variable, die auto enthält, wird jedes Vorkommen von auto durch einen imaginären Typ-Template-Parameter U ersetzt, oder, falls die Initialisierung eine Copy-List-Initialisierung ist, durch std::initializer_list<U> . Das Argument A ist der Initialisierungsausdruck. Nach der Ableitung von U aus P und A gemäß den oben beschriebenen Regeln wird das abgeleitete U in P eingesetzt, um den tatsächlichen Variablentyp zu erhalten:

const auto& x = 1 + 2; // P = const U&, A = 1 + 2:
                       // dieselben Regeln wie für den Aufruf f(1 + 2), wobei f
                       // template<class U> void f(const U& u) ist
                       // abgeleitetes U = int, der Typ von x ist const int&
auto l = {13}; // P = std::initializer_list<U>, A = {13}:
               // abgeleitetes U = int, der Typ von l ist std::initializer_list<int>

Bei der Direkt-List-Initialisierung (jedoch nicht bei der Copy-List-Initialisierung), wenn die Bedeutung von auto aus einer Initialisierungsliste in geschweiften Klammern abgeleitet wird, muss die Initialisierungsliste nur ein Element enthalten, und der Typ von auto wird der Typ dieses Elements sein:

auto x1 = {3}; // x1 ist std::initializer_list<int>
auto x2{1, 2}; // Fehler: kein einzelnes Element
auto x3{3};    // x3 ist int
               // (vor N3922 waren x2 und x3 beide std::initializer_list<int>)
(seit C++11)

Auto-returning-Funktionen

Template-Argument-Deduktion wird in Deklarationen von Funktionen verwendet, wenn die Bedeutung des auto -Spezifizierers im Rückgabetyp der Funktion aus der return-Anweisung abgeleitet wird.

Für Auto-returning-Funktionen wird der Parameter P wie folgt erhalten: In T , dem deklarierten Rückgabetyp der Funktion, der auto enthält, wird jedes Vorkommen von auto durch einen imaginären Typ-Template-Parameter U ersetzt. Das Argument A ist der Ausdruck der return -Anweisung, und wenn die return-Anweisung keinen Operanden hat, ist A void ( ) . Nach der Deduktion von U aus P und A gemäß den oben beschriebenen Regeln wird das abgeleitete U in T substituiert, um den tatsächlichen Rückgabetyp zu erhalten:

auto f() { return 42; } // P = auto, A = 42:
                        // deduced U = int, the return type of f is int

Wenn eine solche Funktion mehrere return-Anweisungen hat, wird die Deduktion für jede return-Anweisung durchgeführt. Alle resultierenden Typen müssen identisch sein und werden zum tatsächlichen Rückgabetyp.

Wenn eine solche Funktion keine return-Anweisung hat, ist A void ( ) bei der Deduktion.

Hinweis: Die Bedeutung des decltype ( auto ) -Platzhalters in Variablen- und Funktionsdeklarationen verwendet keine Template-Argument-Deduktion.

(seit C++14)

Überladenauflösung

Template-Argument-Deduktion wird während der Überladungsauflösung verwendet, wenn Spezialisierungen aus einer Kandidaten-Template-Funktion generiert werden. P und A sind dieselben wie in einem regulären Funktionsaufruf:

std::string s;
std::getline(std::cin, s);
// "std::getline" benennt 4 Funktions-Templates,
// davon sind 2 Kandidaten-Funktionen (korrekte Parameteranzahl)
// 1. Kandidaten-Template:
// P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s
// Deduktion bestimmt die Typ-Template-Parameter CharT, Traits und Allocator
// Spezialisierung std::getline<char, std::char_traits<char>, std::allocator<char>>
// 2. Kandidaten-Template:
// P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s
// Deduktion bestimmt die Typ-Template-Parameter CharT, Traits und Allocator
// Spezialisierung std::getline<char, std::char_traits<char>, std::allocator<char>>
// Überladungsauflösung stuft die Referenzbindung vom Lvalue std::cin ein
// und wählt die erste der beiden Kandidaten-Spezialisierungen

Wenn die Deduktion fehlschlägt oder wenn die Deduktion erfolgreich ist, aber die erzeugte Spezialisierung ungültig wäre (zum Beispiel ein überladener Operator, dessen Parameter weder Klassen- noch Enumerationstypen sind), wird die Spezialisierung nicht in den Überladungssatz aufgenommen, ähnlich wie bei SFINAE .

Adresse einer Überladungsmenge

Template Argument Deduction wird verwendet, wenn die Adresse eines überladenen Sets genommen wird, was Funktions-Templates einschließt.

Der Funktionstyp der Funktionsvorlage ist P . Der Zieltyp ist der Typ von A :

std::cout << std::endl;
// std::endl bezeichnet eine Funktionsvorlage
// Typ von endl P =
// std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&)
// operator<< Parameter A =
// std::basic_ostream<char, std::char_traits<char>>& (*)(
//   std::basic_ostream<char, std::char_traits<char>>&
// )
// (andere Überladungen von operator<< sind nicht geeignet)
// Deduktion bestimmt die Typ-Template-Parameter CharT und Traits

Eine zusätzliche Regel wird in diesem Fall auf die Deduktion angewendet: Beim Vergleichen von Funktionsparametern P i und A i, wenn irgendein P i eine Rvalue-Referenz auf einen cv-unqualifizierten Template-Parameter ist (eine "Forwarding Reference") und das entsprechende A i eine Lvalue-Referenz ist, dann wird P i zum Template-Parametertyp angepasst (T&& wird zu T).

Wenn der Rückgabetyp der Funktionsvorlage ein Platzhalter ist ( auto oder decltype ( auto ) ), ist dieser Rückgabetyp ein nicht abgeleiteter Kontext und wird durch die Instanziierung bestimmt.

(seit C++14)

Partielle Ordnung

Template-Argument-Deduktion wird während der partiellen Ordnung überladener Funktions-Templates verwendet.

Konvertierungsfunktionsvorlage

Template Argument Deduction wird verwendet, wenn user-defined conversion function Template-Argumente ausgewählt werden.

A ist der Typ, der als Ergebnis der Konvertierung benötigt wird. P ist der Rückgabetyp der Konvertierungsfunktionsvorlage. Wenn P ein Referenztyp ist, dann wird der referenzierte Typ anstelle von P für die folgenden Teile dieses Abschnitts verwendet.

Wenn A kein Referenztyp ist:

a) wenn P ein Array-Typ ist, dann wird der durch Array-zu-Pointer-Konvertierung erhaltene Pointer-Typ anstelle von P verwendet;
b) wenn P ein Funktionstyp ist, dann wird der durch Funktion-zu-Zeiger-Konvertierung erhaltene Funktionszeigertyp anstelle von P verwendet;
c) wenn P cv-qualifiziert ist, werden die obersten CV-Qualifizierer ignoriert.

Wenn A cv-qualifiziert ist, werden die obersten CV-Qualifizierer ignoriert. Wenn A ein Referenztyp ist, wird der referenzierte Typ für die Deduktion verwendet.

Falls die übliche Deduktion von P und A (wie oben beschrieben) fehlschlägt, werden zusätzlich die folgenden Alternativen in Betracht gezogen:

a) wenn A ein Referenztyp ist, A kann stärker cv-qualifiziert sein als das abgeleitete A ;
b) wenn A ein Zeiger- oder Zeiger-auf-Member-Typ ist, darf das abgeleitete A jeder Zeiger sein, der durch Qualifikationskonvertierung in A umgewandelt werden kann:
struct C
{
    template<class T>
    operator T***();
};
C c;
const int* const* const* p1 = c;
// P = T***, A = const int* const* const*
// reguläre Funktionsaufruf-Deduktion für
// template<class T> void f(T*** p) als wäre sie mit dem Argument
// vom Typ const int* const* const* aufgerufen, schlägt fehl
// zusätzliche Deduktion für Konvertierungsfunktionen bestimmt T = int
// (abgeleitetes A ist int***, konvertierbar zu const int* const* const*)
c) wenn A ein Funktionszeigertyp ist, darf der abgeleitete A ein Zeiger auf eine noexcept-Funktion sein, der durch Funktionszeigerkonvertierung in A konvertierbar ist;
d) wenn A ein Zeiger auf eine Elementfunktion ist, darf der abgeleitete A ein Zeiger auf eine noexcept-Elementfunktion sein, der durch Funktionszeigerkonvertierung in A konvertierbar ist.
(seit C++17)

Siehe member template für weitere Regeln bezüglich Conversion Function Templates.

Explizite Instanziierung

Template Argument Deduction wird verwendet in expliziten Instanziierungen , expliziten Spezialisierungen und jenen Friend-Deklarationen , bei denen der Deklarator-ID zufällig auf eine Spezialisierung einer Funktions-Template verweist (zum Beispiel, friend ostream & operator << <> ( ... ) ), falls nicht alle Template-Argumente explizit angegeben oder vorgegeben sind, wird Template Argument Deduction verwendet, um zu bestimmen, auf welche Template-Spezialisierung verwiesen wird.

P ist der Typ der Funktionsvorlage, die als potenzielle Übereinstimmung in Betracht gezogen wird, und A ist der Funktionstyp aus der Deklaration. Wenn es keine Übereinstimmungen oder mehr als eine Übereinstimmung (nach partieller Ordnung) gibt, ist die Funktionsdeklaration fehlerhaft:

template<class X>
void f(X a);        // 1. Template f
template<class X>
void f(X* a);       // 2. Template f
template<>
void f<>(int* a) {} // Explizite Spezialisierung von f
// P1 = void(X), A1 = void(int*): abgeleitet X = int*, f<int*>(int*)
// P2 = void(X*), A2 = void(int*): abgeleitet X = int, f<int>(int*)
// f<int*>(int*) und f<int>(int*) werden dann der partiellen Ordnung unterzogen
// welche f<int>(int*) als das spezialisiertere Template auswählt

Eine zusätzliche Regel wird in diesem Fall auf die Deduktion angewendet: Beim Vergleichen von Funktionsparametern P i und A i, wenn irgendein P i eine Rvalue-Referenz auf einen cv-unqualifizierten Template-Parameter ist (eine "Forwarding Reference") und das entsprechende A i eine Lvalue-Referenz ist, dann wird P i zum Template-Parametertyp angepasst (T&& wird zu T).

Deallokationsfunktions-Template

Template-Argument-Deduktion wird verwendet, um zu bestimmen, ob eine Deallocation-Funktion -Template-Spezialisierung mit einer gegebenen Placement-Form von operator new übereinstimmt.

P ist der Typ der Funktionsvorlage, die als potenzielle Übereinstimmung betrachtet wird, und A ist der Funktionstyp der Deallokationsfunktion, die die Übereinstimmung für den betrachteten Placement-Operator new wäre. Wenn keine Übereinstimmung oder mehr als eine Übereinstimmung vorliegt (nach Überladungsauflösung), wird die Placement-Deallokationsfunktion nicht aufgerufen (Speicherleck kann auftreten):

struct X
{
    X() { throw std::runtime_error(""); }
    static void* operator new(std::size_t sz, bool b)   { return ::operator new(sz); }
    static void* operator new(std::size_t sz, double f) { return ::operator new(sz); }
    template<typename T>
    static void operator delete(void* ptr, T arg)
    {
        ::operator delete(ptr);
    }
};
int main()
{
    try
    {
        X* p1 = new (true) X; // wenn X() wirft, wird operator delete gesucht
                              // P1 = void(void*, T), A1 = void(void*, bool):
                              // abgeleitetes T = bool
                              // P2 = void(void*, T), A2 = void(void*, double):
                              // abgeleitetes T = double
                              // Überladungsauflösung wählt operator delete<bool>
    }
    catch(const std::exception&) {}
    try
    {
        X* p1 = new (13.2) X; // gleiche Suche, wählt operator delete<double>
    }
    catch(const std::exception&) {}
}

Alias-Vorlagen

Alias-Templates werden nicht abgeleitet , außer bei Class Template Argument Deduction (seit C++20) :

template<class T>
struct Alloc {};
template<class T>
using Vec = vector<T, Alloc<T>>;
Vec<int> v;
template<template<class, class> class TT>
void g(TT<int, Alloc<int>>);
g(v); // OK: TT = vector abgeleitet
template<template<class> class TT>
void f(TT<int>);
f(v); // Fehler: TT kann nicht als "Vec" abgeleitet werden, da Vec ein Alias-Template ist

Implizite Konvertierungen

Die Typableitung berücksichtigt keine impliziten Konvertierungen (außer den oben aufgeführten Typanpassungen): Das ist die Aufgabe der Überlagerungsauflösung , die später stattfindet. Wenn jedoch die Ableitung für alle Parameter, die an der Template-Argumentableitung teilnehmen, erfolgreich ist, und alle nicht abgeleiteten Template-Argumente explizit angegeben oder standardmäßig sind, dann werden die verbleibenden Funktionsparameter mit den entsprechenden Funktionsargumenten verglichen. Für jeden verbleibenden Parameter P mit einem Typ, der vor der Substitution von explizit angegebenen Template-Argumenten nicht abhängig war, schlägt die Ableitung fehl, wenn das entsprechende Argument A nicht implizit zu P konvertiert werden kann.

Parameter mit abhängigen Typen, an denen keine Template-Parameter an der Template-Argumentableitung teilnehmen, und Parameter, die durch Substitution explizit angegebener Template-Argumente nicht-abhängig geworden sind, werden während der Überladungsauflösung überprüft:

template<class T>
struct Z { typedef typename T::x xx; };
template<class T>
typename Z<T>::xx f(void*, T); // #1
template<class T>
void f(int, T);                // #2
struct A {} a;
int main()
{
    f(1, a); // für #1: Deduktion bestimmt T = struct A, aber das verbleibende Argument 1
             // kann nicht implizit zu seinem Parameter void* konvertiert werden: Deduktion scheitert
             // Instanziierung des Rückgabetyps wird nicht angefordert
             // für #2: Deduktion bestimmt T = struct A, und das verbleibende Argument 1
             // kann implizit zu seinem Parameter int konvertiert werden: Deduktion erfolgreich
             // der Funktionsaufruf kompiliert als Aufruf von #2 (Deduktionsfehler ist SFINAE)
}

Fehlerberichte

Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR Angewendet auf Verhalten wie veröffentlicht Korrektes Verhalten
CWG 70 C++98 ob Arraygrenzen abgeleitet werden, war nicht spezifiziert als nicht abgeleitet spezifiziert
CWG 300 C++98 Ableitung fand statt für Funktionsparameter der Form
type(*)(T)/T(*)()/T(*)(T) , Funktionszeiger
passen zu diesen Formen, Funktionsreferenzen jedoch nicht
diese Formen geändert zu
type(T)/T()/T(T) damit sie
auch Referenzen abdecken können
CWG 322 C++98 Typparameter von Referenztypen wurden nicht
angepasst, um den referenzierten Typ für die Ableitung zu verwenden
Anpassung hinzugefügt
CWG 976 C++98 bei der Ableitung für Konvertierungsoperatoren-Templates
const T& Rückgabetyp konnte niemals mit T Ergebnistyp übereinstimmen
Regeln angepasst um
solche Übereinstimmungen zu erlauben
CWG 1387 C++11 der Ausdruck eines decltype-Spezifizierers war kein nicht-abgeleiteter Kontext ist er
CWG 1391 C++98 Effekt von impliziten Konvertierungen der Argumente
die nicht an der Ableitung beteiligt sind, war nicht spezifiziert
wie oben beschrieben spezifiziert
CWG 1591 C++11 kann Arraygrenze und Elementtyp nicht von einer braced-init-list ableiten Ableitung erlaubt
CWG 2052 C++98 Ableiten eines Operators mit Nicht-Klassen
Nicht-Enum-Argumenten war ein schwerer Fehler
weicher Fehler wenn
andere Overloads vorhanden sind
CWG 2091 C++98 Ableiten eines Referenz-Konstanten-Parameters funktionierte nicht
aufgrund von Typinkongruenz mit dem Argument
Typinkongruenz vermieden
N3922 C++11 Direct-List-Initialisierung von auto leitet std::initializer_list ab ill-formed für mehr als ein
Element, leitet Elementtyp
für einzelnes Element ab
CWG 2355 C++17 Wert in einem noexcept Spezifizierer eines Funktionstyps war nicht ableitbar ableitbar gemacht