名稱空間
變體
操作

模板引數推導

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 
 
 

為了例項化函式模板,必須知道每個模板實參,但並非每個模板實參都必須顯式指定。在可能的情況下,編譯器會從函式實參推導缺失的模板實參。這發生在嘗試函式呼叫、獲取函式模板地址以及在一些其他語境中。

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
}

此機制使得使用模板運算子成為可能,因為除了將其重寫為函式呼叫表示式之外,沒有語法可以為運算子指定模板實參。

#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>>
}

模板實參推導發生在函式模板名稱查詢(可能涉及實參依賴查詢)之後,以及模板實參替換(可能涉及SFINAE)和過載決議之前。

當類模板的名稱用作正在構造的物件的型別時,也會執行模板實參推導。

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

類模板的模板實參推導發生在宣告和顯式型別轉換表示式中;有關詳細資訊,請參閱類模板實參推導

(C++17 起)

目錄

[編輯] 從函式呼叫中推導

模板實參推導嘗試確定模板實參(型別模板引數Ti的型別,模板模板引數TTi的模板,非型別模板引數Ii的值),這些實參可以替換到每個引數P中,以生成推匯出的型別A,該型別與實參A的型別相同,但在下方列出的調整後。

如果存在多個引數,則每個P/A對都單獨推導,然後組合推匯出的模板實參。如果推導失敗或任何P/A對模稜兩可,或者如果不同的對產生不同的推導模板實參,或者如果任何模板實參既未推導也未顯式指定,則編譯失敗。

如果從P中移除引用和cv限定符得到std::initializer_list<P'>,並且A大括號初始化列表,則對初始化列表的每個元素執行推導,將P'作為引數,將列表元素A'作為實參。

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

如果從P中移除引用和cv限定符得到P'[N],並且A是非空的大括號初始化列表,則推導如上所述執行,但如果N是非型別模板引數,則從初始化列表的長度中推導。

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

如果引數包作為最後一個P出現,則型別P將與呼叫中每個剩餘實參的型別A匹配。每個匹配都推匯出引數包擴充套件中下一個位置的模板實參。

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>
}


(C++11 起)

如果P是函式型別、指向函式型別的指標或指向成員函式型別的指標,並且A是不包含函式模板的過載函式集,則嘗試對每個過載進行模板實參推導。如果只有一個成功,則使用該成功推導。如果沒有或多於一個成功,則模板引數是非推導語境(見下文)。

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

在推導開始之前,對PA進行以下調整:

1) 如果P不是引用型別,
a) 如果A是陣列型別,則A替換為從陣列到指標轉換獲得的指標型別;
b) 否則,如果A是函式型別,則A替換為從函式到指標轉換獲得的指標型別;
c) 否則,如果A是cv限定型別,則頂層cv限定符在推導中被忽略。
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) 如果P是cv限定型別,則頂層cv限定符在推導中被忽略。
3) 如果P是引用型別,則引用型別用於推導。
4) 如果P是對cv非限定模板引數的右值引用(所謂的轉發引用),並且對應的函式呼叫實參是左值,則推導時使用對A的左值引用型別代替A(注意:這是std::forward行為的基礎。注意:在類模板實參推導中,類模板的模板引數永遠不是轉發引用(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
}

在這些轉換之後,推導過程如下所述(參見從型別中推導一節),並嘗試查詢這樣的模板實參,使得推匯出的A(即,在上述調整和推匯出的模板引數替換之後的P)與轉換後的A(即,在上述調整之後的A)相同。

如果PA的通常推導失敗,則額外考慮以下替代方案:

1) 如果P是引用型別,則推匯出的A(即,引用所引用的型別)可以比轉換後的A具有更多的cv限定符。
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) 轉換後的A可以是另一種指標或指向成員的指標型別,可以透過限定轉換或函式指標轉換(C++17 起)轉換為推匯出的A
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) 如果P是類且P具有簡單模板ID形式,則轉換後的A可以是推匯出的A的派生類。同樣,如果P是指向簡單模板ID形式的類的指標,則轉換後的A可以是指向由推匯出的A指向的派生類的指標。
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
}

[編輯] 非推導語境

在以下情況下,用於構成P的型別、模板和非型別值不參與模板實參推導,而是使用在其他地方推導或顯式指定的模板實參。如果模板引數僅在非推導語境中使用且未顯式指定,則模板實參推導失敗。

1) 使用限定ID指定的型別的巢狀名稱說明符(範圍解析運算子::左側的一切)
// 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) 包索引說明符包索引表示式
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
(C++26 起)
3) decltype說明符的表示式
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
(C++11 起)
4) 其中子表示式引用模板引數的非型別模板實參或陣列邊界
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) 函式引數的引數型別中使用的模板引數,該引數具有在進行實參推導的呼叫中使用的預設實參
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) 引數P,其A是函式或過載集,使得多於一個函式與P匹配,或者沒有函式與P匹配,或者過載集包含一個或多個函式模板。
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) 引數P,其A是大括號初始化列表,但P不是std::initializer_list、對它的引用(可能cv限定)或對陣列的引用。
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) 引數P,它是引數包且未出現在引數列表的末尾。
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) 出現在引數P內的模板引數列表,並且其中包含的包擴充套件不位於模板引數列表的最末尾。
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
(C++11 起)
10) 對於陣列型別的P(但不是陣列引用或指向陣列的指標),主陣列邊界。
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
}

在任何情況下,如果型別名稱的任何部分是非推導的,則整個型別名稱是非推導語境。然而,複合型別可以包括推導和非推導型別名稱。例如,在A<T>::B<T2>中,T是非推導的,因為規則#1(巢狀名稱說明符),而T2是非推導的,因為它是相同型別名稱的一部分,但在void(*f)(typename A<T>::B, A<T>)中,A<T>::B中的T是非推導的(因為相同的規則),而A<T>中的T是推導的。

[編輯] 從型別中推導

給定一個函式引數P,它依賴於一個或多個型別模板引數Ti、模板模板引數TTi或非型別模板引數Ii,以及相應的實參A,如果P具有以下形式之一,則進行推導:

  • cv(可選) T;
  • T*;
  • T&;
  • T&&;
(C++11 起)
  • T(可選) [I(可選)];
  • T(可選) (U(可選));
(C++17 前)
  • T(可選) (U(可選)) noexcept(I(可選));
(C++17 起)
  • T(可選) U(可選)::*;
  • TT(可選)<T>;
  • TT(可選)<I>;
  • TT(可選)<TU>;
  • TT(可選)<>

在上述形式中,

  • T(可選)U(可選) 表示一個型別或引數型別列表,它要麼遞迴地滿足這些規則,要麼在PA中是一個非推導語境,要麼在PA中是相同的非依賴型別。
  • TT(可選)TU(可選) 表示一個類模板或模板模板引數。
  • I(可選) 表示一個表示式,它要麼是一個I,要麼在PA中是值依賴的,要麼在PA中具有相同的常量值。
  • noexcept(I(可選)) 表示一個異常說明符,其中可能隱式的noexcept說明符的運算元滿足上述I(可選)的規則。
(C++17 起)

如果P具有包含模板引數列表<T><I>的形式之一,則該模板實參列表的每個元素Pi都會與其A的相應模板實參Ai匹配。如果最後一個Pi是包擴充套件,則其模式會與A的模板實參列表中每個剩餘實參進行比較。未被其他方式推導的尾隨引數包被推導為空引數包。

如果P具有包含函式引數列表(T)的形式之一,則該列表中的每個引數Pi都會與A的函式引數列表中相應的實參Ai進行比較。如果最後一個Pi是包擴充套件,則其宣告符會與A的引數型別列表中每個剩餘的Ai進行比較。

形式可以巢狀並遞迴處理。

  • X<int>(*)(char[6])T*的一個例子,其中TX<int>(char[6])
  • X<int>(char[6])T(可選) (U(可選))的一個例子,其中TX<int>Uchar[6]
(C++17 前)
  • X<int>(char[6])T(可選) (U(可選)) noexcept(I(可選))的一個例子,其中TX<int>Uchar[6],並且隱式noexcept說明符中的Ifalse
(C++17 起)
  • X<int>TT(可選)<T>的一個例子,其中TTXTint,並且
  • char[6]T(可選) [I(可選)]的一個例子,其中TcharIstd::size_t(6)

型別模板實參不能從非型別模板實參的型別中推導。

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
(C++17 前)

當與宣告為依賴型別的非型別模板引數P對應的實參的值從表示式中推導時,P型別中的模板引數從該值型別中推導。

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>

T[N]型別中N的型別是std::size_t

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

函式型別中noexcept(B)說明符中B的型別是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.
(C++17 起)

如果函式模板的非型別模板引數在函式引數(也是模板)的模板引數列表中使用,並且推導了相應的模板實參,則推匯出的模板實參的型別(如其包圍的模板引數列表中指定,意味著引用被保留)必須與非型別模板引數的型別完全匹配,除了cv限定符被丟棄,以及模板實參是從陣列邊界推導的情況——在這種情況下,允許任何整型,甚至bool,儘管它總是變為true

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
}

型別模板引數不能從函式預設實參的型別中推導。

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)
}

模板模板引數的推導可以使用函式呼叫中使用的模板特化中使用的型別。

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

[編輯] 其他語境

除了函式呼叫和運算子表示式之外,模板實參推導還用於以下情況:

auto 型別推導

在變數的宣告中,當從變數的初始化器推導auto 說明符的含義時,會使用模板實參推導。

引數P的獲取方式如下:在包含auto的變數的宣告型別T中,auto的每次出現都替換為一個虛構的型別模板引數U,或者,如果初始化是複製列表初始化,則替換為std::initializer_list<U>。實參A是初始化表示式。在按照上述規則從PA推導U之後,將推匯出的U替換到P中以獲得實際的變數型別。

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>

在直接列表初始化中(但不在複製列表初始化中),當從大括號初始化列表中推導auto的含義時,大括號初始化列表必須只包含一個元素,並且auto的型別將是該元素的型別。

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>)
(C++11 起)

auto 返回函式

函式的宣告中,當從return語句推導函式返回型別中auto說明符的含義時,會使用模板實參推導。

對於auto返回函式,引數P的獲取方式如下:在包含auto的函式的宣告返回型別T中,auto的每次出現都替換為一個虛構的型別模板引數U。實參Areturn語句的表示式,如果return語句沒有運算元,則Avoid()。在按照上述規則從PA推導U之後,將推匯出的U替換到T中以獲得實際的返回型別。

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

如果此類函式有多個return語句,則對每個return語句執行推導。所有結果型別必須相同併成為實際的返回型別。

如果此類函式沒有return語句,則在推導時,Avoid()

注意:變數和函式宣告中decltype(auto)佔位符的含義不使用模板實參推導。

(C++14 起)

[編輯] 過載決議

過載決議期間,從候選模板函式生成特化時會使用模板實參推導。PA與常規函式呼叫中相同。

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

如果推導失敗,或者推導成功但其生成的特化將無效(例如,引數既非類也非列舉型別的過載運算子),則該特化不包含在過載集中,類似於SFINAE

[編輯] 過載集的地址

當獲取包含函式模板的過載集的地址時,會使用模板實參推導。

函式模板的函式型別是P目標型別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

在這種情況下,推導會應用一條額外規則:當比較函式引數Pi和Ai時,如果任何Pi是對cv非限定模板引數的右值引用(“轉發引用”),並且相應的Ai是左值引用,則將Pi調整為模板引數型別(T&&變為T)。

如果函式模板的返回型別是佔位符(autodecltype(auto)),則該返回型別是非推導語境,並從例項化中確定。

(C++14 起)

[編輯] 偏序

過載函式模板的偏序期間會使用模板實參推導。

[編輯] 轉換函式模板

在選擇使用者定義轉換函式模板實參時會使用模板實參推導。

A是作為轉換結果所需的型別。P是轉換函式模板的返回型別。如果P是引用型別,則在以下部分中,引用型別用於代替P

如果A不是引用型別:

a) 如果P是陣列型別,則透過陣列到指標轉換獲得的指標型別用於代替P
b) 如果P是函式型別,則透過函式到指標轉換獲得的函式指標型別用於代替P
c) 如果P是cv限定的,則頂層cv限定符被忽略。

如果A是cv限定的,則頂層cv限定符被忽略。如果A是引用型別,則引用型別用於推導。

如果PA的通常推導(如上所述)失敗,則額外考慮以下替代方案:

a) 如果A是引用型別,則A可以比推匯出的A具有更多的cv限定符;
b) 如果A是指標或指向成員的指標型別,則推匯出的A允許是任何可以透過限定轉換轉換為A的指標;
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) 如果A是函式指標型別,則推匯出的A允許是指向noexcept函式的指標,可以透過函式指標轉換轉換為A
d) 如果A是指向成員函式的指標,則推匯出的A允許是指向noexcept成員函式的指標,可以透過函式指標轉換轉換為A
(C++17 起)

有關轉換函式模板的其他規則,請參閱成員模板

[編輯] 顯式例項化

顯式例項化顯式特化以及那些宣告符ID恰好引用函式模板特化的友元宣告(例如,friend ostream& operator<< <> (...))中,如果並非所有模板實參都顯式指定或已預設,則使用模板實參推導來確定引用哪個模板的特化。

P是正在考慮作為潛在匹配的函式模板的型別,A是宣告中的函式型別。如果沒有匹配或多於一個匹配(在偏序之後),則函式宣告格式錯誤。

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

在這種情況下,推導會應用一條額外規則:當比較函式引數Pi和Ai時,如果任何Pi是對cv非限定模板引數的右值引用(“轉發引用”),並且相應的Ai是左值引用,則將Pi調整為模板引數型別(T&&變為T)。

[編輯] 解分配函式模板

當確定解分配函式模板特化是否與給定形式的`operator new`匹配時,會使用模板實參推導。

P是正在考慮作為潛在匹配的函式模板的型別,A是在考慮的放置new運算子的匹配解分配函式的函式型別。如果沒有匹配或多於一個匹配(在過載決議之後),則不呼叫放置解分配函式(可能會發生記憶體洩漏)。

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&) {}
}

[編輯] 別名模板

別名模板不被推導,除非在類模板實參推導(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

[編輯] 隱式轉換

型別推導不考慮隱式轉換(除了上面列出的型別調整):那是過載決議的工作,它發生在後面。然而,如果對於所有參與模板實參推導的引數,推導都成功,並且所有未推導的模板實參都顯式指定或已預設,則將剩餘的函式引數與相應的函式實參進行比較。對於每個具有在替換任何顯式指定的模板實參之前是非依賴型別的剩餘引數P,如果相應的實參A不能隱式轉換為P,則推導失敗。

具有依賴型別但沒有模板引數參與模板實參推導的引數,以及由於替換顯式指定的模板實參而變為非依賴的引數將在過載決議期間檢查。

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)
}

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 70 C++98 未指定是否推導陣列邊界 指定為非推導
CWG 300 C++98 對以下形式的函式引數進行推導
type(*)(T)/T(*)()/T(*)(T),函式指標
匹配這些形式,但函式引用不匹配
將這些形式更改為
type(T)/T()/T(T)以便它們
也可以覆蓋引用
CWG 322 C++98 引用型別的型別引數未
調整為使用引用型別進行推導
添加了調整
CWG 976 C++98 在轉換運算子模板的推導中,
const T&返回型別永遠無法匹配T結果型別
規則調整為
允許此類匹配
CWG 1387 C++11 decltype-specifier的表示式不是非推導語境 它是
CWG 1391 C++98 未指定不參與推導的
實參的隱式轉換效果
如上所述指定
CWG 1591 C++11 無法從大括號初始化列表推導陣列邊界和元素型別 允許推導
CWG 2052 C++98 推導具有非類
非列舉實參的運算子是硬錯誤
如果有其他過載,則為軟錯誤
有其他過載
CWG 2091 C++98 由於與實參型別不匹配,無法推導
引用非型別引數
避免型別不匹配
N3922 C++11 auto的直接列表初始化推匯出std::initializer_list 對於多個元素格式錯誤,推導單個元素的元素型別
元素,推導單個元素的
元素型別
CWG 2355 C++17 函式型別noexcept說明符中的值不可推導 變得可推導