模板实参推导

来自cppreference.com
< cpp‎ | language

为实例化一个函数模板,必须知晓每个模板实参,但不必指定每个模板实参。可能时,编译器会从函数实参推导缺失的模板实参。这发生在尝试调用函数、取函数模板地址时,和某些其他语境中:

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

此机制使得使用模板运算符可行,因为为运算符指定模板实参,无重写之为函数调用表达式以外的语法:

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

参数包作为最后的 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>
}

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*
const int b = 13;
f(b); // P = T , A = const int ,调整为 int :推出 T = int
void g(int);
f(g); // P = T , A = void(int) ,调整为 void(*)(int) :推出 T = void(*)(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 拥有形式 simple-template-id ,则变换的 A 能为推导的 A 的导出类。类似地,若 P 是指向 simple-template-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>& ,调整为 P = B<T> (简单模板 id ), A = D<int> :
          // 推导 T = int ,推导 A = B<int>
          // A 从推导的 A 导出
}

[编辑] 非推导语境

下列情况下,用于组成 P 的类型、模板和非类型值不参与模板实参推导,但取而代之地使用会在别处推导或显式指定的模板实参。若模板形参仅用于非推导语境,且未显式指定,则模板实参推导失败。

1)有限定 id 指定的类型的 nested-name-specifier (作用域解析运算符 :: 左侧的所有内容):
// 同一性模板,常用于从推导排除特定实参
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) decltype 指定符的表达式:
template<typename T> void f(decltype(*std::declval<T>()) arg);
int n;
f<int*>(n); // P = decltype(*declval<T>()) , A = int : T 在非推导语境
(C++14 起)
3) 非类型模板实参或数组边界,其中子表达式引用一个模板

parameter:

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
4) 用于函数形参的形参类型中的模板形参,该函数形参在正在进行实参推导的调用中,拥有默认实参:
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) 中正在使用的默认实参
5) 形参 P ,其实参 A 是函数,或满足多于一个函数匹配 P 或无函数匹配 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 在非推导语境
6) 形参 P ,其实参 A 是花括号初始化器列表,但 P 非 std::initializer_list 或到它的引用:
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
7) 作为参数包且不出现在形参列表尾部的形参 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 在非推导语境
8) 出现在形参 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> 在非推导语境
9) 数组(但不是到数组引用或指向数组指针)类型 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>::B 中的 T 为非推导(因为相同规则),不过 A<T> 中的 T 可推导。

[编辑] 从类型推导

给定依赖一或多个类型模板形参 Ti 、模板模板形参 TTi 或非类型模板形参 Ii 的模板形参 P 和对应的实参 A ,若 P 拥有下列形式之一则发生推导:

  • T;
  • cv-list T;
  • T*;
  • T&;
  • T&&;
  • T[integer-constant];
  • class-template-name<T>;
  • type(T);
  • T();
  • T(T);
  • T type::*;
  • type T::*;
  • T T::*;
  • T(type::*)();
  • type(T::*)();
  • type(type::*)(T);
  • type(T::*)(T);
  • T (type::*)(T);
  • T (T::*)();
  • T (T::*)(T);
  • type[i];
  • class-template-name<I>;
  • TT<T>;
  • TT<I>;
  • TT<>;

其中

  • (T) 是其中至少有一个参数类型含 T 的函数参数列表;
  • () 是其中无参数含 T 的函数参数列表;
  • <T> 是至少一个实参含 T 的模板实参列表;
  • <I> 是其中至少一个实参含 I 的模板实参列表;
  • <> 其中无实参含 T 或 I 的模板实参列表。

P 拥有包含模板形参列表 <T><I> 的形式之一,则将该模板形参列表的每个元素 Pi 与其 A 的对应模板实参 Ai 匹配。若最后一个 Pi 是包展开,则将其模式与 A 的模板实参列表中的每个剩余实参比较。其他情况下不推导的尾随参数包,被推导为空参数包。

P 拥有包含函数参数列表 (T) ,则将来自该列表的每个形参 Pi 与来自 A 的函数参数列表的对应实参 A 比较。若最后一个 Pi 是包展开,则比较其声明器和 A 形参类型列表中的每个剩余的 Ai 。

形式能嵌套并递归处理: X<int>(*)(char[6])type(*)(T) 的例子,其中 typeclass-template-name<T> 且 T 为 type[i]

模板类型实参不能从非类型模板实参推导:

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

类型 NT[N] 的类型是 std::size_t

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

若非类型模板形参被用于形参列表中,且推导了对应的模板实参,则推导的模板实参类型(指定于其外围模板形参列表中,这表示保持引用)必须准确匹配非类型模板形参的类型,除了丢掉 cv 限定符,且除了从数组边界推导的模板实参——此情况下允许任何整数类型,即使是会始终为 true 的 bool :

template<int i> class A { };
template<short s> void f(A<s>); // 非类型模板形参的类型是 short
 
void k1()
{
    A<1> 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 是模板,拥有形参 TT
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 , calls f(A<B>)

[编辑] 其他语境

在函数调用和运算符表达式外,在下列情形使用模板实参推导:

[编辑] auto 类型推导

从变量的初始化器推导 auto 指定符的含义时,模板实参推导用于变量声明

以下列方式获得形参 P :在包含 auto 的变量的被声明类型 T 中, auto 的每次出现都被替换为虚构类型模板形参 U ,或若初始化器是花括号初始化器列表,则为 std::initializer_list<U> 。实参 A 是初始化器表达式。按上述规则从 PA 推导 U 后,将推导出的 U 替换进 T ,以获取实际变量类型:

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 ( C++17 前为 std::initializer_list<int> )
(C++17 起)

返回 auto 的函数

从 return 语句推导函数返回类型中 auto 指定符的含义时,将模板实参推导用于函数的声明。

对于返回 auto 的函数,以下列方式获得形参 P :在包含 auto 的被声明函数返回类型 T 中, auto 的每次出现都被替换为虚构类型模板实参 U 。实参 Areturn 语句的表达式,且若 return 语句无运算数,则 Avoid() 。按上述规则从 PA 推导 U 后,将推导出的 U 替换进 T ,以获取实际返回类型:

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

若这种函数拥有多个 return 语句,则对每个 return 语句进行推导。所有结果类型必须相同,并成为实际返回类型。

若这种函数无 return 语句,则推导时 Avoid()

注意:变量和函数声明中的 decltype(auto) 占位符的含义不使用模板实参推导。

(C++14 起)

[编辑] 重载决议

从候选模板函数生成特化时,在重载决议期间使用模板实参推导:

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 绑定的引用排在高位
// 并选取二个候选特化的第一个

若推导失败,或若推导成功,但它产生的特化会非法(例如形参既非类类型亦非枚举类型的重载运算符), (C++14 起)则特化不包含于重载集,类似 SFINAE

[编辑] 重载集地址

取包含函数模板的重载集地址时,使用模板实参推导。

函数模板的函数类型为 P目标类型A 的类型:

std::cout << std::endl; // std::endl 指名函数模板
// 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>>&
// )
// (其他 operator<< 的重载不可成活)
// 推导确定类型模板实参 CharT 和 Traits

此情况下应用额外的规则到推导:比较函数形参 Pi 和 Ai 时,若任何 Pi 是到无 cv 限定模板形参的右值引用(“转发引用”)且对应的 Ai 为左值引用,则调整 Pi 为模板形参类型( T&& 成为 T )。

若函数模板的返回类型是占位符( autodecltype(auto) ),则返回类型在非推导语境,并从实例化决定。

(C++14 起)

[编辑] 偏排序

重载的函数模板的偏排序期间使用模板实参推导。

[编辑] 转换函数模板

选择用户定义转换函数模板实参时使用模板实参推导。

A 是要求为转换结果的类型。 P 是转换函数模板的返回类型,除了

a) 若返回类型是引用类型,则 P 为被引用类型;
b) 若返回类型是数组类型且 A 非引用类型,则 P 为从数组到指针转换获得的指针类型;
c) 若返回类型为函数类型且 A 非引用类型,则 P 为从函数到指针转换获得的函数指针类型;
d)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 起)

关于转换函数模板的其他规则见成员模板

[编辑] 显式实例化

模板实参推导用于显式实例化显式特化 及声明器 id 恰好指代函数模板特化的友元声明(例如 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&) { }
}

[编辑] 别名模版

决不推导别名模版

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++14 起)

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 1391 C++14 未指定推导所不涉及的实参隐式转换的效果 指定为上述
CWG 2052 C++14 以非类实参推导运算符是硬错误(某些编译器中) 若有其他重载则为软错误
CWG 2091 C++98 推导引用非类型形参不可用,由于类型不能匹配实参 避免类型不匹配