Namensräume
Varianten
Aktionen

Schablonen-Argument-Deduktion

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 
 
 

Um eine Funktionstemplates zu instanziieren, müssen alle Template-Argumente bekannt sein, aber nicht alle Template-Argumente müssen angegeben werden. Wenn möglich, leitet der Compiler die fehlenden Template-Argumente aus den Funktionsargumenten ab. Dies geschieht, wenn ein Funktionsaufruf versucht wird, wenn eine Adresse eines Funktionstemplate genommen wird, und in einigen anderen Kontexten.

template<typename To, typename From>
To convert(From f);
 
void g(double d)
{
    int i = convert<int>(d);    // calls convert<int, double>(double)
    char c = convert<char>(d);  // calls convert<char, double>(double)
    int(*ptr)(float) = convert; // instantiates convert<int, float>(float)
                                // and stores its address 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 ihn als Ausdruck eines Funktionsaufrufs neu zu schreiben.

#include <iostream>
 
int main()
{
    std::cout << "Hello, world" << std::endl;
    // operator<< is looked up via ADL as std::operator<<,
    // then deduced to operator<<<char, std::char_traits<char>> both times
    // std::endl is deduced to &std::endl<char, std::char_traits<char>>
}

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

Die 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);

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

(seit C++17)

Inhalt

[bearbeiten] Deduktion aus einem Funktionsaufruf

Die Template-Argument-Deduktion versucht, Template-Argumente (Typen für Typ-Template-Parameter Ti, Templates für Template-Template-Parameter TTi und Werte für Nicht-Typ-Template-Parameter Ii) zu bestimmen, die in jeden Parameter P substituiert werden können, um den Typ deduziert A zu erzeugen, der dem Typ des Arguments A entspricht, nach den unten aufgeführten Anpassungen.

Wenn es mehrere Parameter gibt, wird jedes P/A-Paar separat deduziert und die deduzierten Template-Argumente werden dann kombiniert. Wenn die Deduktion für ein P/A-Paar fehlschlägt oder mehrdeutig ist, oder wenn verschiedene Paare unterschiedliche deduzierte Template-Argumente ergeben, oder wenn ein Template-Argument weder deduziert noch explizit angegeben bleibt, schlägt die Kompilierung fehl.

Wenn Referenzen und cv-Qualifizierer von P entfernt werden und std::initializer_list<P'> ergibt und A eine geschweifte Initialisierungsliste ist, dann wird die Deduktion für jedes Element der Initialisierungsliste durchgeführt, wobei P' als Parameter und das Listen-Element A' als Argument genommen 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 Referenzen und cv-Qualifizierer von P entfernt werden und P'[N] ergibt und A eine nicht-leere geschweifte Initialisierungsliste ist, dann wird die Deduktion wie oben durchgeführt, außer wenn N ein Nicht-Typ-Template-Parameter ist, dann wird er 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 eine Parameter-Pack als letztes P erscheint, dann wird der Typ P gegen den Typ A jedes verbleibenden Arguments des Aufrufs abgeglichen. Jede Übereinstimmung 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, ein Zeiger auf einen Funktionstyp oder ein Zeiger auf einen Member-Funktionstyp ist und A eine Menge überladener Funktionen ist, die keine Funktionstemplates enthält, wird die 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 deduzierbarer Kontext (siehe unten).

template<class T>
int f(T(*p)(T));
 
int g(int);
int g(char);
 
f(g); // P = T(*)(T), A = overload set
      // P = T(*)(T), A1 = int(int): deduced T = int
      // P = T(*)(T), A2 = int(char): fails to deduce T
      // only one overload works, deduction succeeds

Bevor die Deduktion beginnt, werden die folgenden Anpassungen an P und A vorgenommen.

1) Wenn P kein Referenztyp ist,
a) wenn A ein Array-Typ ist, wird A durch den Zeigertyp ersetzt, der durch Array-zu-Zeiger-Konvertierung erhalten wird;
b) andernfalls, wenn A ein Funktionstyp ist, wird A durch den Zeigertyp ersetzt, der durch Funktions-zu-Zeiger-Konvertierung erhalten wird;
c) andernfalls, wenn A ein cv-qualifizierter Typ ist, werden die Top-Level-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 Top-Level-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 Weiterleitungsreferenzen) und das entsprechende Funktionsaufrufargument 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 Aktion von std::forward. Hinweis: In der Klassen-Template-Argument-Deduktion ist der Template-Parameter eines Klassentemplates niemals eine Weiterleitungsreferenz(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 erfolgt die Deduktion wie unten beschrieben (vgl. Abschnitt Deduktion aus 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) mit dem *transformierten* A, d. h. A nach den oben aufgeführten Anpassungen, identisch machen.

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

1) Wenn P ein Referenztyp ist, kann das deduzierte A (d. h. der von der Referenz referenzierte Typ) cv-qualifizierter 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) Das transformierte A kann ein anderer Zeiger oder Zeiger auf Member-Typ sein, der durch eine Qualifizierungskonvertierung oder eine Funktionszeigerkonvertierung(seit C++17) in das deduzierte 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 eines einfachen Template-IDs hat, kann das transformierte A eine abgeleitete Klasse des deduzierten A sein. Ebenso, wenn P ein Zeiger auf eine Klasse der Form einfache Template-ID ist, kann das transformierte A ein Zeiger auf eine abgeleitete Klasse sein, auf die vom deduzierten A gezeigt wird.
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
}

[bearbeiten] Nicht deduzierbare Kontexte

In den folgenden Fällen nehmen die Typen, Templates und Nicht-Typ-Werte, die zur Zusammensetzung von P verwendet werden, nicht an der Template-Argument-Deduktion teil, sondern *verwenden* stattdessen die Template-Argumente, die entweder woanders deduziert oder explizit angegeben wurden. Wenn ein Template-Parameter nur in nicht deduzierbaren Kontexten verwendet und nicht explizit angegeben wird, schlägt die Template-Argument-Deduktion fehl.

1) Der verschachtelte Namensspezifizierer (alles links vom Scope-Auflösungsoperator ::) eines Typs, der mit einer qualifizierten ID angegeben 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-Indexing-Spezifizierer oder ein Pack-Indexing-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 Nicht-Typ-Template-Argument oder eine Array-Grenze, in der ein Unterausdruck 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 die Argument-Deduktion 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, so dass mehr als eine Funktion zu P passt oder keine Funktion zu P passt oder die Menge der Überladungen ein oder mehrere Funktionstemplates 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 geschweifte Initialisierungsliste ist, aber P kein std::initializer_list ist, eine Referenz darauf (möglicherweise cv-qualifiziert) oder eine Referenz auf ein Array ist.
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 is in non-deduced context
                   // error: T is not explicitly specified or deduced from another P/A
 
g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T is in non-deduced context
                   // P2 = T, A2 = int: deduced T = int
8) Der Parameter P, der ein Parameter-Pack 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 Pack-Expansion 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 ein Teil eines Typnamens nicht deduzierbar ist, ist der gesamte Typname ein nicht deduzierbarer Kontext. Zusammengesetzte Typen können jedoch sowohl deduzierbare als auch nicht deduzierbare Typnamen enthalten. Zum Beispiel ist in A<T>::B<T2> T nicht deduzierbar wegen Regel #1 (verschachtelter Namensspezifizierer) und T2 ist nicht deduzierbar, weil es Teil desselben Typnamens ist. Aber in void(*f)(typename A<T>::B, A<T>) ist das T in A<T>::B nicht deduzierbar (wegen derselben Regel), während das T in A<T> deduziert wird.

[bearbeiten] Deduktion aus einem Typ

Gegeben einen Funktionsparameter P, der von einem oder mehreren Typ-Template-Parametern Ti, Template-Template-Parametern TTi oder Nicht-Typ-Template-Parametern Ii abhängt, und dem entsprechenden Argument A, findet die 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) stellt einen Typ oder eine Parameter-Typ-Liste dar, die entweder diese Regeln rekursiv erfüllt, ein nicht deduzierbarer Kontext in P oder A ist oder derselbe nicht-abhängige Typ in P und A ist.
  • TT(optional) oder TU(optional) stellt entweder ein Klassentemplate oder ein Template-Template-Parameter dar.
  • I(optional) stellt einen Ausdruck dar, der entweder ein I ist, in P oder A wertabhängig ist oder denselben konstanten Wert in P und A hat.
  • noexcept(I(optional)) stellt eine Ausnahme-Spezifikation dar, bei der der möglicherweise implizite Operand des noexcept-Spezifizierers 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> enthalten, dann wird jedes Element Pi dieser Template-Argumentenliste mit dem entsprechenden Template-Argument Ai von seinem A abgeglichen. Wenn das letzte Pi eine Pack-Expansion ist, dann wird sein Muster mit jedem verbleibenden Argument in der Template-Argumentenliste von A verglichen. Ein nachgestellter Parameter-Pack, der nicht anderweitig deduziert wird, wird als leerer Parameter-Pack deduziert.

Wenn P eine der Formen hat, die eine Funktionsparameterliste (T) enthalten, dann wird jeder Parameter Pi aus dieser Liste mit dem entsprechenden Argument Ai aus der Funktionsparameterliste von A verglichen. Wenn das letzte Pi eine Pack-Expansion ist, dann wird sein Deklarator mit jedem verbleibenden Ai in der Parameter-Typ-Liste von A verglichen.

Formen können verschachtelt und rekursiv verarbeitet werden.

  • X<int>(*)(char[6]) ist ein Beispiel für T*, wobei T X<int>(char[6]) ist;
  • X<int>(char[6]) ist ein Beispiel für T(optional) (U(optional)), wobei T X<int> und U char[6] ist;
(bis C++17)
  • X<int>(char[6]) ist ein Beispiel für T(optional) (U(optional)) noexcept(I(optional)), wobei T X<int>, U char[6] ist und I im impliziten noexcept-Spezifizierer false ist;
(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-Argumente können nicht aus dem Typ eines Nicht-Typ-Template-Arguments deduziert 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 can be deduced to equal 20
      // but T cannot be deduced from the type of i
(bis C++17)

Wenn der Wert des Arguments, das einem Nicht-Typ-Template-Parameter P entspricht, der mit einem abhängigen Typ deklariert ist, aus einem Ausdruck deduziert wird, werden die Template-Parameter im Typ von P aus dem Typ des Wertes deduziert.

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 was deduced to long
                      // from the template argument value in the type A<2>
 
template<auto X>
class bar {};
 
template<class T, T n>
void f(bar<n> x);
 
f(bar<3>{}); // OK: T was deduced to int (and n to 3)
             // from the template argument value in the type 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 is std::size_t

Der Typ von B im 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 is deduced as true and the type of X is deduced as bool.
(seit C++17)

Wenn ein Nicht-Typ-Template-Parameter eines Funktionstemplate in der Template-Parameterliste eines Funktionparameters (der ebenfalls ein Template ist) verwendet wird und das entsprechende Template-Argument deduziert wird, muss der Typ des deduzierten Template-Arguments (wie in seiner umschließenden Template-Parameterliste angegeben, d. h. Referenzen werden beibehalten) exakt dem Typ des Nicht-Typ-Template-Parameters entsprechen, außer dass cv-Qualifizierer weggelassen werden, und außer wenn das Template-Argument aus einer Array-Grenze deduziert wird – in diesem Fall ist jeder integrale Typ zulässig, sogar bool, obwohl dieser immer zu true würde.

template<int i>
class A {};
 
template<short s>
void f(A<s>); // the type of the non-type template param is short
 
void k1()
{
    A<1> a;  // the type of the non-type template param of a is int
 
    f(a);    // P = A<(short)s>, A = A<(int)1>
             // error: deduced non-type template argument does not have the same
             // type as its corresponding template argument
 
    f<1>(a); // OK: the template argument is not deduced,
             // this calls f<(short)1>(A<(short)1>)
}
 
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>
           // parameter type is int&
           // argument type is int& in struct X's template declaration
           // OK (with CWG 2091): deduces R to refer to n
}

Typ-Template-Parameter können nicht aus dem Typ eines Standard-Funktionsarguments deduziert werden.

template<typename T>
void f(T = 5, T = 7);
 
void g()
{
    f(1);     // OK: calls f<int>(1, 7)
    f();      // error: cannot deduce T
    f<int>(); // OK: calls f<int>(5, 7)
}

Die Deduktion 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 is a template with a TT param
 
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>: deduced TT = B, calls f(A<B>)

[bearbeiten] Andere Kontexte

Neben Funktionsaufrufen und Operator-Ausdrücken wird die Template-Argument-Deduktion in den folgenden Situationen verwendet.

auto-Typ-Deduktion

Die Template-Argument-Deduktion wird in Deklarationen von Variablen verwendet, wenn die Bedeutung des auto-Spezifizierers aus dem Initialisierer der Variablen deduziert wird.

Der Parameter P wird wie folgt erhalten: In T, dem deklarierten Typ der Variablen, die auto enthält, wird jede Vorkommen von auto durch einen imaginären Typ-Template-Parameter U ersetzt oder, wenn die Initialisierung eine Kopier-Listen-Initialisierung ist, durch std::initializer_list<U>. Das Argument A ist der Initialisiererausdruck. Nach der Deduktion von U aus P und A gemäß den oben beschriebenen Regeln wird das deduzierte U in P substituiert, um den tatsächlichen Variablentyp zu erhalten.

const auto& x = 1 + 2; // P = const U&, A = 1 + 2:
                       // same rules as for calling f(1 + 2) where f is
                       // template<class U> void f(const U& u)
                       // deduced U = int, the type of x is const int&
 
auto l = {13}; // P = std::initializer_list<U>, A = {13}:
               // deduced U = int, the type of l is std::initializer_list<int>

Bei direkter Listeninitialisierung (aber nicht bei Kopier-Listen-Initialisierung), wenn die Bedeutung von auto aus einer geschweiften Initialisierungsliste deduziert wird, muss die geschweifte Initialisierungsliste nur ein Element enthalten, und der Typ von auto ist der Typ dieses Elements.

auto x1 = {3}; // x1 is std::initializer_list<int>
auto x2{1, 2}; // error: not a single element
auto x3{3};    // x3 is int
               // (before N3922 x2 and x3 were both std::initializer_list<int>)
(seit C++11)

auto-zurückgebende Funktionen

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

Bei auto-zurückgebenden Funktionen wird der Parameter P wie folgt erhalten: In T, dem deklarierten Rückgabetyp der Funktion, die 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 deduzierte 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 gleich sein und werden zum tatsächlichen Rückgabetyp.

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

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

(seit C++14)

[bearbeiten] Überladungsauflösung

Die 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 bei einem regulären Funktionsaufruf.

std::string s;
std::getline(std::cin, s);
 
// "std::getline" names 4 function templates,
// 2 of which are candidate functions (correct number of parameters)
 
// 1st candidate template:
// P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s
// deduction determines the type template parameters CharT, Traits, and Allocator
// specialization std::getline<char, std::char_traits<char>, std::allocator<char>>
 
// 2nd candidate template:
// P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s
// deduction determines the type template parameters CharT, Traits, and Allocator
// specialization std::getline<char, std::char_traits<char>, std::allocator<char>>
 
// overload resolution ranks reference binding from lvalue std::cin
// and picks the first of the two candidate specializations

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

[bearbeiten] Adresse einer Überladungsmenge

Die Template-Argument-Deduktion wird beim Ermitteln einer Adresse einer Überladungsmenge verwendet, die Funktionstemplates enthält.

Der Funktionstyp des Funktionstemplate ist P. Der Zieltyp ist der Typ von A.

std::cout << std::endl;
 
// std::endl names a function template
// type of 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>>&
// )
// (other overloads of operator<< are not viable) 
// deduction determines the type template parameters CharT and Traits

Eine zusätzliche Regel wird auf die Deduktion in diesem Fall angewendet: Beim Vergleichen von Funktionsparametern Pi und Ai, wenn ein Pi eine rvalue-Referenz auf einen cv-unqualifizierten Template-Parameter ist (eine "Weiterleitungsreferenz") und das entsprechende Ai eine lvalue-Referenz ist, dann wird Pi auf den Template-Parametertyp (T&& wird zu T) angepasst.

Wenn der Rückgabetyp des Funktionstemplate ein Platzhalter ist (auto oder decltype(auto)), ist dieser Rückgabetyp ein nicht deduzierbarer Kontext und wird aus der Instanziierung ermittelt.

(seit C++14)

[bearbeiten] Partielle Ordnung

Die Template-Argument-Deduktion wird während der partiellen Ordnung von überladenen Funktionstemplates verwendet.

[bearbeiten] Konvertierungsfunktions-Template

Die Template-Argument-Deduktion wird bei der Auswahl von benutzerdefinierten Konvertierungsfunktions-Template-Argumenten verwendet.

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

Wenn A kein Referenztyp ist

a) Wenn P ein Array-Typ ist, dann wird der Zeigertyp, der durch Array-zu-Zeiger-Konvertierung erhalten wird, anstelle von P verwendet;
b) Wenn P ein Funktionstyp ist, dann wird der Funktionzeigertyp, der durch Funktions-zu-Zeiger-Konvertierung erhalten wird, anstelle von P verwendet;
c) Wenn P cv-qualifiziert ist, werden die Top-Level-cv-Qualifizierer ignoriert.

Wenn A cv-qualifiziert ist, werden die Top-Level-cv-Qualifizierer ignoriert. Wenn A ein Referenztyp ist, wird der referenzierte Typ von der Deduktion verwendet.

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

a) Wenn A ein Referenztyp ist, kann A cv-qualifizierter sein als das deduzierte A;
b) Wenn A ein Zeiger oder Zeiger auf Member-Typ ist, darf das deduzierte A jeder Zeiger sein, der durch Qualifizierungskonvertierung in A konvertiert werden kann.
struct C
{
    template<class T>
    operator T***();
};
C c;
 
const int* const* const* p1 = c;
 
// P = T***, A = const int* const* const*
// regular function-call deduction for
// template<class T> void f(T*** p) as if called with the argument
// of type const int* const* const* fails
// additional deduction for conversion functions determines T = int
// (deduced A is int***, convertible to const int* const* const*)
c) Wenn A ein Funktionzeigertyp ist, darf das deduzierte A ein Zeiger auf eine noexcept-Funktion sein, die durch Funktionszeigerkonvertierung in A konvertierbar ist;
d) Wenn A ein Zeiger auf eine Member-Funktion ist, darf das deduzierte A ein Zeiger auf eine noexcept-Member-Funktion sein, der durch Funktionszeigerkonvertierung in A konvertierbar ist.
(seit C++17)

Siehe Member-Templates für weitere Regeln bezüglich Konvertierungsfunktions-Templates.

[bearbeiten] Explizite Instanziierung

Die Template-Argument-Deduktion wird in expliziten Instanziierungen, expliziten Spezialisierungen und jenen friend-Deklarationen verwendet, bei denen der Deklarator-ID zufällig auf eine Spezialisierung eines Funktionstemplate verweist (z. B. friend ostream& operator<< <> (...)), wenn nicht alle Template-Argumente explizit angegeben oder standardmäßig gesetzt sind, wird die Template-Argument-Deduktion verwendet, um zu bestimmen, welche Template-Spezialisierung referenziert wird.

P ist der Typ des Funktionstemplate, das als potenzieller Treffer betrachtet wird, und A ist der Funktionstyp aus der Deklaration. Wenn es keine Übereinstimmungen oder mehr als eine Übereinstimmung gibt (nach partieller Ordnung), ist die Funktionsdeklaration fehlerhaft.

template<class X>
void f(X a);        // 1st template f
template<class X>
void f(X* a);       // 2nd template f
template<>
void f<>(int* a) {} // explicit specialization of f
 
// P1 = void(X), A1 = void(int*): deduced X = int*, f<int*>(int*)
// P2 = void(X*), A2 = void(int*): deduced X = int, f<int>(int*)
// f<int*>(int*) and f<int>(int*) are then submitted to partial ordering
// which selects f<int>(int*) as the more specialized template

Eine zusätzliche Regel wird auf die Deduktion in diesem Fall angewendet: Beim Vergleichen von Funktionsparametern Pi und Ai, wenn ein Pi eine rvalue-Referenz auf einen cv-unqualifizierten Template-Parameter ist (eine "Weiterleitungsreferenz") und das entsprechende Ai eine lvalue-Referenz ist, dann wird Pi auf den Template-Parametertyp (T&& wird zu T) angepasst.

[bearbeiten] Deallokationsfunktionstemplates

Die Template-Argument-Deduktion wird verwendet, um zu bestimmen, ob eine Deallokationsfunktion-Template-Spezialisierung zu einer gegebenen Platzierungsform von operator new passt.

P ist der Typ des Funktionstemplate, das als potenzieller Treffer betrachtet wird, und A ist der Funktionstyp der Deallokationsfunktion, die der Platzierungsoperator new unter Berücksichtigung entsprechen würde. Wenn es keine Übereinstimmung oder mehr als eine Übereinstimmung gibt (nach Überladungsauflösung), wird die Platzierungs-Deallokationsfunktion nicht aufgerufen (Speicherverlust 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; // when X() throws, operator delete is looked up
                              // P1 = void(void*, T), A1 = void(void*, bool):
                              // deduced T = bool
                              // P2 = void(void*, T), A2 = void(void*, double):
                              // deduced T = double
                              // overload resolution picks operator delete<bool>
    }
    catch(const std::exception&) {}
 
    try
    {
        X* p1 = new (13.2) X; // same lookup, picks operator delete<double>
    }
    catch(const std::exception&) {}
}

[bearbeiten] Alias-Templates

Alias-Templates werden nicht deduziert, außer bei der Klassen-Template-Argument-Deduktion(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: deduced TT = vector
 
template<template<class> class TT>
void f(TT<int>);
f(v); // error: TT cannot be deduced as "Vec" because Vec is an alias template

[bearbeiten] Implizite Konvertierungen

Die Typableitung berücksichtigt keine impliziten Konvertierungen (außer den oben genannten Typanpassungen): Dies ist die Aufgabe der Überladungsauflösung, die später stattfindet. Wenn jedoch die Ableitung für alle Parameter, die an der Template-Argumentableitung teilnehmen, erfolgreich ist und alle Template-Argumente, die nicht abgeleitet werden, explizit angegeben oder standardmäßig gesetzt 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 in P konvertiert werden kann.

Parameter mit abhängigen Typen, bei denen keine Template-Parameter an der Template-Argumentableitung teilnehmen, und Parameter, die aufgrund der Substitution von explizit angegebenen Template-Argumenten nicht mehr abhängig 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); // for #1, deduction determines T = struct A, but the remaining argument 1
             // cannot be implicitly converted to its parameter void*: deduction fails
             // instantiation of the return type is not requested
             // for #2, deduction determines T = struct A, and the remaining argument 1
             // can be implicitly converted to its parameter int: deduction succeeds
             // the function call compiles as a call to #2 (deduction failure is SFINAE)
}

[bearbeiten] Defect reports

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 70 C++98 ob Array-Grenzen abgeleitet werden würden, war nicht spezifiziert als nicht ableitbar spezifiziert
CWG 300 C++98 die Ableitung fand für Funktionsparameter der Form statt
type(*)(T)/T(*)()/T(*)(T), Funktionszeiger
stimmen mit diesen Formen überein, Funktionsreferenzen jedoch nicht
diese Formen zu
type(T)/T()/T(T) geändert, damit sie
auch Referenzen abdecken können
CWG 322 C++98 Typ-Parameter 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 Konvertierungsoperator-Templates
konnte der Rückgabetyp const T& nie mit dem Ergebnistyp T übereinstimmen
Regeln angepasst, um
solche Übereinstimmungen zu ermöglichen
CWG 1387 C++11 der Ausdruck eines decltype-Spezifizierers war kein nicht ableitbarer Kontext sie ist
CWG 1391 C++98 die Auswirkung von impliziten Konvertierungen der Argumente
die nicht an der Ableitung beteiligt sind, war nicht spezifiziert
wie oben beschrieben spezifiziert
CWG 1591 C++11 Array-Grenze und Elementtyp können nicht aus einer initializer list abgeleitet werden Ableitung erlaubt
CWG 2052 C++98 die Ableitung eines Operators mit Nicht-Klassen
Nicht-Enum-Argumenten war ein harter Fehler
weicher Fehler, wenn es
andere Überladungen gibt
CWG 2091 C++98 die Ableitung eines Referenz-Nicht-Typ-Parameters funktionierte nicht
aufgrund eines Typkonflikts mit dem Argument
Typkonflikt vermieden
N3922 C++11 Direkt-List-Initialisierung von auto leitet std::initializer_list ab fehlerhaft für mehr als ein
Element, leitet Elementtyp
für ein einzelnes Element ab
CWG 2355 C++17 der Wert in einem noexcept-Spezifizierer eines Funktionstyps war nicht ableitbar ableitbar gemacht