模板实参推导

出自cppreference.com
< cpp‎ | language


 
 
C++ 語言
 
 

為了實例化一個函數模板需要知曉每個模板實參,但並非每個模板實參都必須予以指定。編譯器會儘可能從函數實參推導缺失的模板實參。這會在嘗試調用函數、取函數模板地址時,和某些其他語境中發生:

template<typename To, typename From>
To convert(From f);
 
void g(double d)
{
    int i = convert<int>(d);    // 调用 convert<int, double>(double)
    char c = convert<char>(d);  // 调用 convert<char, double>(double)
    int(*ptr)(float) = convert; // 实例化 convert<int, float>(float)
                                // 并将它的地址储存在 ptr
}

運算符模板依賴此機制運行,因為除了將它重寫為函數調用表達式之外,不存在為運算符指定模板實參的語法:

#include <iostream>
 
int main()
{
    std::cout << "Hello, world" << std::endl;
    // operator<< 通过 ADL 查找为 std::operator<<,
    // 然后两次都推导为 operator<<<char, std::char_traits<char>>
    // std::endl 被推导为 &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:推导出 T = int
               // P'2 = T,A'2 = 2:推导出 T = int
               // P'3 = T,A'3 = 3:推导出 T = int
               // OK:推导出 T = int
 
f({1, "abc"}); // P = std::initializer_list<T>,A = {1, "abc"}
               // P'1 = T,A'1 = 1:推导出 T = int
               // P'2 = T,A'2 = "abc":推导出 T = const char*
               // 错误:推导失败,T 有歧义

如果 P 在移除引用和 cv 限定符後得到 P'[N] 且 A 是非空花括號初始化器列表,那麼按上述方法進行推導,但如果 N 是常量模板形參,那麼它會從初始化器列表的長度被推導:

template<class T, int N>
void h(T const(&)[N]);
h({1, 2, 3}); // 推导出 T = int,推导出 N = 3
 
template<class T>
void j(T const(&)[3]);
j({42}); // 推导出 T = int,数组边界不是形参,未予考虑
 
struct Aggr
{
    int i;
    int j;
};
 
template<int N>
void k(Aggr const(&)[N]);
k({1, 2, 3});       // 错误:推导失败,没有从 int 到 Aggr 的转换
k({{1}, {2}, {3}}); // OK:推导出 N = 3
 
template<int M, int N>
void m(int const(&)[M][N]);
m({{1, 2}, {3, 4}}); // 推导出 M = 2,推导出 N = 2
 
template<class T, int N>
void n(T const(&)[N], T);
n({{1}, {2}, {3}}, Aggr()); // 推导出 T = Aggr,推导出 N = 3

如果形參包作為最後的 P 出現,那麼對調用的每個剩餘實參類型 A 與類型 P 匹配。每個匹配為包展開中的下個位置推導模板實參:

template<class... Types>
void f(Types&...);
 
void h(int x, float& y)
{
    const int z = x;
    f(x, y, z); // P = Types&..., A1 = x:推导出 Types... 的第一成员 = int
                // P = Types&..., A2 = y:推导出 Types... 的第二成员 = float
                // P = Types&..., A3 = z:推导出 Types... 的第三成员 = const int
                // 调用 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 = 重载集
      // P = T(*)(T),A1 = int(int):推导出 T = int
      // P = T(*)(T),A2 = int(char):无法推导 T
      // 只有一个重载有效,推导成功

推導開始前,對 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],调整为 int*:推导出 T = int*
 
void b(int);
f(b); // P = T,A = void(int),调整为 void(*)(int):推导出 T = void(*)(int)
 
const int c = 13;
f(c); // P = T,A = const int,调整为 int:推导出 T = int
2) 如果 P 是有 cv 限定的類型,那麼推導時會忽略頂層 cv 限定符。
3) 如果 P 是引用類型,那麼用 P 所引用的類型推導。
4) 如果 P 是到無 cv 限定模板形參的右值引用(也就是轉發引用)且對應函數的調用實參是左值,那麼將到 A 的左值引用類型用於 A 的位置進行推導(注意:這是 std::forward 的行動基礎。注意:類模板實參推導中,類模板的模板形參不可能是轉發引用(C++17 起)。):
template<class T>
int f(T&&);       // P 是到无 cv 限定类型 T 的右值引用(转发引用)
 
template<class T>
int g(const T&&); // P 是到有 cv 限定 T 的右值引用(非特殊)
 
int main()
{
    int i;
    int n1 = f(i); // 实参是左值:调用 f<int&>(int&) (特殊情况)
    int n2 = f(0); // 实参不是左值:调用 f<int>(int&&)
 
//  int n3 = g(i); // 错误:推导出 g<int>(const int&&),它不能绑定右值引用到左值
}

在進行這些變換之後,按以下步驟進行推導處理(參閱從類型推導一節)並試圖找到能讓推導的 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&,调整为 const T,A = bool:
      // 推导出 T = bool,推导出 A = const bool
      // 推导出的 A 比(原来的)A 更受 cv 限定
2) 變換後的 A 可以具有另一指針或成員指針類型,並可以通過限定轉換或函數指針轉換(C++17 起)轉換到推導出的 A
template<typename T>
void f(const T*);
 
int* p;
f(p); // P = const T*,A = int*:
      // 推导出 T = int,推导出 A = const int*
      // 应用限定转换(从 int* 到 const int*)
3) 如果 P 是類且 P 的形式為 簡單模板標識,那麼變換後的 A 可以是推導出的 A 的派生類。類似地,如果 P 是指向 簡單模板標識 形式的類的指針,那麼變換後的 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>&,调整为 P = B<T>(简单模板标识),A = D<int>:
          // 推导出 T = int,推导出 A = B<int>
          // (原来的)A 派生自推导出的 A 
}

[編輯] 不推導語境

下列情況下,用來組成 P 的類型、模板和常量值不會參與模板實參推導,而改為使用可以在別處推導出或顯式指定的模板實參。如果模板形參只在不推導語境使用且沒有被顯式指定,那麼模板實參推導失敗。

1)有限定標識指定的類型的 嵌套名說明符(作用域解析運算符 :: 左側的所有內容):
// 恒等模板,常用于从推导中排除特定实参
// (即 C++20 中新增的 std::type_identity )
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:推导出 T = std::complex<double>
              // P2 = T,A2 = double
              // P2/A2:推导出 T = double
              // 错误:推导失败,T 有歧义
 
good(x, 1.2); // P1 = std::vector<T>,A1 = std::vector<std::complex<double>>
              // P1/A1:推导出 T = std::complex<double>
              // P2 = identity<T>::type,A2 = double
              // P2/A2:使用 P1/A1 推导出的 T,因为 T 在 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: 推导出 Ts... 的第一个成员 = int
// P2/A2: 推导出 Ts... 的第二个成员 = char
// P1 = Ts...[0], A1 = int: Ts...[0] 是不推导语境
(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 处于不推导语境
(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 处于不推导语境,无法推导出 N
      // 注意:f(std::array<int, N> a) 可以推导出 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> 左值
      // P1/A1 推导出 T = std::string
      // P2 = const F&,A2 = std::less<std::string> 右值
      // P2 在用于函数形参 comp 的形参类型(const F&)的 F(模板形参)的不推导语境,
      // 该函数形参拥有调用 f(v) 中正在使用的默认实参
6) 形參 P,其所對應的實參 A 是一個函數,沒有函數或有多個函數與 P 匹配的重載集,或包含一個或多個函數模板的重載集:
template<typename T>
void out(const T& value) { std::cout << value; }
 
out("123");     // P = const T&,A = const char[4] 左值:推导出 T = char[4]
out(std::endl); // P = const T&,A = 函数模板:T 处于不推导语境
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 在不推导语境中
                   // 错误:T 没有被显式指定或从另一对 P/A 推导出
 
g2({1, 2, 3}, 10); // P1 = std::vector<T>,A1 = {1, 2, 3}:T 在不推导语境中
                   // P2 = T,A2 = int:推导出 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:推导出 T = int
                // P2 = Ts...,A2 = 2,A3 = 3,A4 = 4:推导出 Ts = [int, int, int]
 
f2(1, 2, 3, 4); // P1 = Ts...:Ts 在不推导语境
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>:
              // 推导出 N = 1 以及 Ts1 = [2]
              // P2 = const T<N, Ts2...>&,A2 = T<1, -1, 0>:
              // 推导出 N = 1 以及 Ts2 = [-1, 0]
 
bad(t1, t2);  // P1 = const T<Ts1..., N>&,A1 = T<1, 2>:
              // <Ts1..., N> 处于不推导语境
              // P2 = const T<Ts2..., N>&,A2 = T<1, -1, 0>:
              // <Ts2..., N> 处于不推导语境
(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],数组类型
 
template<int i>
void f3(int (&a)[i][20]); // P = int(&)[i][20],到数组的引用
 
void g()
{
    int a[10][20];
    f1(a);     // OK:推导出 i = 20
    f1<20>(a); // OK
    f2(a);     // 错误:i 处于不推导语境
    f2<10>(a); // OK
    f3(a);     // OK:推导出 i = 10
    f3<10>(a); // OK
}

在任何情況下,如果類型名的任何部分不會被推導,那麼整個類型處於不推導語境。然而複合類型可以同時包含推導和不推導的類型名。例如在 A<T>::B<T2> 中,T 因為規則 #1(嵌套類型說明符)而不會被推導,且 T2 因為它是同一類型名的一部分也不會被推導,但在 void(*f)(typename A<T>::B, A<T>) 中,在 A<T> 中的 T 會被推導時 A<T>::B 中的 T 也(因為相同規則)不會被推導。

[編輯] 從類型推導

給定依賴一或多個類型模板形參 Ti、模板模板形參 TTi 或常量模板形參 Ii 的模板形參 P 及其對應實參 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 能被推导为等于 20
      // 但不能从 i 的类型推导 T
(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:从类型 A<2> 中的模板实参值推导出 T = long
 
template<auto X>
class bar{};
 
template<class T, T n>
void f(bar<n> x);
 
f(bar<3>{}); // OK:从类型 bar<3> 中的模板实参值推导出 T = int(以及 n = 3)

類型 t[N]N 的類型是 std::size_t

template<class T, T i>
void f(int (&a)[i]);
 
int v[10];
f(v); // OK:T 是 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 为 true 并推导 X 的类型为 bool。
(C++17 起)

如果在形參列表中使用某個常量模板形參並推導了它對應的模板實參,那麼推導出的模板實參類型(如同在它的外圍模板形參列表中指定,這表示引用被保留)必須與該常量模板形參的類型嚴格匹配,但 cv 限定符被丟棄,且不包括從數組邊界推導的模板實參——此時允許任何包括 bool 在內(雖然它總是變為 true)的整數類型:

template<int i>
class A {};
 
template<short s>
void f(A<s>); // 常量模板形参的类型是 short
 
void k1()
{
    A<1> a;  // a 的常量模板形参的类型是 int
 
    f(a);    // P = A<(short)s>, A = A<(int)1>
             // 错误:推导出的常量模板形参拥有与对应模板实参不同的类型
 
    f<1>(a); // OK:不推导模板实参,这会调用 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>
           // struct X 的模板声明中形参类型是 int&
           // 实参类型是 int&
           // OK(因 CWG 2091):推导出 R 以指代 n
}

類型模板形參不能從函數默認實參的類型推導:

template<typename T>
void f(T = 5, T = 7);
 
void g()
{
    f(1);     // OK:调用 f<int>(1, 7)
    f();      // 错误:无法推导出 T
    f<int>(); // OK:调用 f<int>(5, 7)
}

模板模板形參的推導可以使用函數調用中所用的模板特化中所使用的類型:

template<template<typename> class X>
struct A {}; // A 是一个拥有模板模板形参的模板
 
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>:推导出 TT = B,调用 f(A<B>)

[編輯] 其他語境

除了函數調用和運算符表達式以外,以下場合也使用模板實參推導:

auto 類型推導

當變量聲明需要從變量的初始化器推導 auto 說明符的含義時會用到模板實參推導。

通過以下方式獲得形參 P:在變量的被聲明類型 T(包含 auto)中,auto 的每次出現都被替換成一個虛構的類型模板形參 U,或在它的初始化是複製列表初始化時替換成 std::initializer_list<U>。實參 A 是初始化器表達式。按上文所述的規則從 PA 推導出 U 後,將推導出的 U 替換到 P 中以獲取實際的變量類型:

const auto& x = 1 + 2; // P = const U&,A = 1 + 2:
                       // 与调用 f(1 + 2) 的规则相同,其中 f 是
                       // template<class U> void f(const U& u)
                       // 推导出 U = int,x 的类型是 const int&
 
auto l = {13}; // P = std::initializer_list<U>,A = {13}:
               // 推导出 U = int,l 的类型是 std::initializer_list<int>

在直接列表初始化(但不是複製列表初始化)中,當從花括號初始化器列表推導 auto 的含義時,花括號初始化器列表必須只含一個元素,而 auto 的類型將是該元素的類型:

auto x1 = {3}; // x1 是 std::initializer_list<int>
auto x2{1, 2}; // 错误:不止单个元素
auto x3{3};    // x3 是 int
               //(N3922 之前,x2 和 x3 都是 std::initializer_list<int>)
(C++11 起)

返回 auto 的函數

當從 return 語句推導函數返回類型中的 auto 說明符的含義時,將模板實參推導用於函數的聲明。

對於返回 auto 的函數,通過以下方式獲得形參 P:在被聲明函數的返回類型 T(包含 auto)中,auto 的每次出現都會被替換成一個虛構的類型模板實參 U。實參 Areturn 語句的表達式,而如果 return 語句沒有操作數,那麼 Avoid()。按上文所述的規則從 PA 推導出 U 後,將推導出的 U 替換到 T 中以獲取實際的返回類型:

auto f() { return 42; } // P = auto,A = 42:
                        // 推导出 U = int,f 的返回类型是 int

如果這種函數擁有多個返回語句,那麼就會對每個返回語句進行推導。所有結果類型必須相同,並成為該函數的實際返回類型。

如果這種函數沒有返回語句,那麼推導時 Avoid()

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

(C++14 起)

[編輯] 重載決議

從候選模板函數生成特化時,在重載決議期間使用模板實參推導: 其 PA 和常規函數調用相同。

std::string s;
std::getline(std::cin, s);
 
// "std::getline" 指名 4 个函数模板,
// 其中 2 个是候选函数(形参数正确)
 
// 第 1 个候选模板:
// P1 = std::basic_istream<CharT, Traits>&,A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&,A2 = s
// 推导确定类型模板实参 CharT、Traits 和 Allocator
// 特化 std::getline<char, std::char_traits<char>, std::allocator<char>>
 
// 第 2 个候选模板:
// P1 = std::basic_istream<CharT, Traits>&&,A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&,A2 = s
// 推导确定类型模板形参 CharT、Traits 和 Allocator
// 特化 std::getline<char, std::char_traits<char>, std::allocator<char>>
 
// 重载决议将从左值 std::cin 绑定的引用排在高位
// 并选取两个候选特化的第一个

如果推導失敗,或推導成功但產生的特化無效(例如形參既既不是類類型也不是枚舉類型的重載運算符),那麼重載集不會包含該特化,這類似於 SFINAE

[編輯] 重載集的地址

取包含函數模板在內的重載集的地址時,使用模板實參推導。

函數模板的函數類型為 P目標類型A 的類型:

std::cout << std::endl;
 
// std::endl 指名函数模板
// endl 的类型 P =
// std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&)
// operator<< 的形参 A =
// std::basic_ostream<char, std::char_traits<char>>& (*)(
//     std::basic_ostream<char, std::char_traits<char>>&
// )
// (其他 operator<< 的重载不可行)
// 推导确定类型模板实参 CharT 和 Traits

這種情況下的推導適用一條額外的規則:當比較函數形參 Pi 和 Ai 時,如果有任何 Pi 是到無 cv 限定模板形參的右值引用(“轉發引用”)且對應的 Ai 是左值引用,那麼會將 Pi 調整為模板形參類型(T&& 成為 T)。

如果函數模板的返回類型是佔位符(autodecltype(auto)),那麼返回類型處於不推導語境,並由實例化決定。

(C++14 起)

[編輯] 偏序

重載的函數模板的偏序過程中使用模板實參推導。

[編輯] 轉換函數模板

選擇用戶定義轉換函數模板實參時使用模板實參推導。

A 是要求作為轉換結果的類型。P 是轉換函數模板的返回類型。如果 P 是引用類型,那麼在本節後續部分出現的 P 表示的是被引用的類型。

如果 A 不是引用類型,那麼:

a) 如果返回類型是數組類型,那麼 P 表示的是從數組到指針轉換獲得的指針類型;
b) 如果返回類型是函數類型,那麼 P 表示的是從函數到指針轉換獲得的函數指針類型;
c) 如果 P 有 cv 限定,那麼忽略頂層 cv 限定符。

如果 A 有 cv 限定,那麼忽略頂層 cv 限定符。如果 A 是引用類型,那麼推導使用被引用的類型。

如果從 PA 進行的常規推導(如上文所述)失敗,那麼考慮下列替代方式:

a) 如果 A 是引用類型,那麼 A 可以比推導出的 A 有更多的 cv 限定;
b) 如果 A 是指針或成員指針類型,那麼推導出的 A 可以是任意能以限定轉換轉換到 A 的指針:
struct A
{
    template<class T>
    operator T***();
};
A a;
 
const int* const* const* p1 = a;
 
// P = T***,A = const int* const* const*
// 对 template<class T> void f(T*** p) 的常规函数调用推导
// (如同以 const int* const* const* 类型的实参进行调用)失败
// 转换函数的额外推导确定 T = int
// (推导出的 A 是 int***,可以转换成 const int* const* const*)
c) 如果 A 是函數指針類型,那麼推導出的 A 可以是指向 noexcept 函數的指針,並可以通過函數指針轉換轉換成 A
d) 如果 A 是成員函數指針,那麼推導出的 A 可以是指向 noexcept 成員函數的指針,並可以通過函數指針轉換轉換成 A
(C++17 起)

關於轉換函數模板的其他規則,見成員模板

[編輯] 顯式實例化

顯式實例化顯式特化以及聲明符標識恰好指代某個函數模板特化的友元聲明(例如 friend ostream& operator<< <> (...))在還有模板實參沒有被顯式指定且沒有默認值時會用模板實參推導來確定指代哪個模板特化。

P 是被認為是潛在匹配的函數模板的類型,而 A 是聲明中的函數類型。如果(偏序後)沒有匹配或多於一個匹配,那麼函數聲明為非良構:

template<class X>
void f(X a);        // 第 1 个模板 f
template<class X>
void f(X* a);       // 第 2 个模板 f
template<>
void f<>(int* a) {} // f 的显式特化
 
// P1 = void(X), A1 = void(int*):推导出 X = int*,f<int*>(int*)
// P2 = void(X*),A2 = void(int*):推导出 X = int, f<int>(int*)
// 向偏序提交 f<int*>(int*) 与 f<int>(int*)
// 它选择 f<int>(int*) 为更特殊的模板

這種情況下的推導適用一條額外的規則:當比較函數形參 Pi 和 Ai 時,如果有任何 Pi 是到無 cv 限定模板形參的右值引用(“轉發引用”)且對應的 Ai 是左值引用,那麼將 Pi 調整為模板形參類型(T&& 成為 T)。

[編輯] 解分配函數模板

確定解分配函數模板特化是否與給定的 operator new 布置形式相匹配時使用模板實參推導。

P 是被認為是潛在匹配的函數模板的類型,而 A 是應當與考慮中的布置 operator 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; // 当 X() 抛出异常时,查找 operator delete
                              // P1 = void(void*, T),A1 = void(void*, bool):
                              // 推导出 T = bool
                              // P2 = void(void*, T),A2 = void(void*, double):
                              // 推导出 T = double
                              // 重载决议挑选 operator delete<bool>
    }
    catch(const std::exception&) {}
 
    try
    {
        X* p1 = new (13.2) X; // 同样的查找,挑选 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:推导出 TT = vector
 
template<template<class> class TT>
void f(TT<int>);
f(v); // 错误:TT 无法被推导为 "Vec",因为 Vec 是别名模版

[編輯] 隱式轉換

類型推導不會考慮(上文列出的類型調整之外的)隱式轉換:這是其後進行的重載決議的工作。然而,如果對所有參與模板實參推導的形參進行的推導均成功,並且所有不推導的模板實參均被顯式指定或有默認值,那麼將剩餘的各函數形參與對應的函數實參比較。對於具有在替換任何顯式指定的模板實參之前未待決的類型的每個剩餘形參 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); // 对于 #1,推导确定 T = struct A,但剩余实参 1
             // 不能隐式转换成其形参 void*:推导失败
             // 不要求返回类型的实例化
             // 对于 #2,推导确定 T = struct A,且剩余实参 1
             // 能隐式转换成其形参 int:推导成功
             // 函数调用编译为到 #2 的调用(推导失败是 SFINAE)
}

[編輯] 缺陷報告

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

缺陷報告 應用於 出版時的行為 正確行為
CWG 70 C++98 未指明數組邊界是否會被推導 指定為不推導
CWG 300 C++98 形式為 类型(*)(T)/T(*)()/T(*)(T) 的函數形參會
參與推導,函數指針可以匹配這些形參,但函數引用不能
將這些形式改為 类型(T)/T()/T(T)
使得函數引用也可以匹配它們
CWG 322 C++98 引用類型的類型形參在推導前不會調整到被引用類型 添加對應調整
CWG 976 C++98 在推導轉換函數模板時無法將返回類型 const T& 與結果類型 T 匹配 修改規則以使之匹配
CWG 1387 C++11 decltype 說明符的表達式不是不推導語境 它是不推導語境
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 說明符中的值曾為不可推導 使之可推導