命名空间
变体
操作

模板参数推导

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
一般主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储持续时间说明符
初始化
 
 
 
 

为了实例化函数模板,必须知道每个模板参数,但并非每个模板参数都必须指定。如果可能,编译器将从函数参数中推导出缺失的模板参数。这发生在尝试进行函数调用时,获取函数模板的地址时,以及在一些其他上下文中

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 的获取方式如下:在 T 中,包含 auto 的变量的声明类型中,每次出现 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 起)

自动返回函数

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

对于自动返回函数,参数 P 的获取方式如下:在 T 中,包含 auto 的函数的声明返回类型中,每次出现 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++ 标准。

DR 应用于 发布时的行为 正确的行为
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 说明符的表达式不是非推导上下文 它是
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 说明符中的值不可推导 使其可推导