模板参数推导
为了实例化函数模板,必须知道每个模板实参,但并非每个模板实参都必须显式指定。在可能的情况下,编译器会从函数实参推导缺失的模板实参。这发生在尝试函数调用、获取函数模板地址以及在一些其他语境中。
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 }
此机制使得使用模板运算符成为可能,因为除了将其重写为函数调用表达式之外,没有语法可以为运算符指定模板实参。
模板实参推导发生在函数模板名称查找(可能涉及实参依赖查找)之后,以及模板实参替换(可能涉及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 起) |
目录 |
[编辑] 从函数调用中推导
模板实参推导尝试确定模板实参(类型模板参数T
i的类型,模板模板参数TT
i的模板,非类型模板参数I
i的值),这些实参可以替换到每个参数P
中,以生成推导出的类型A
,该类型与实参A
的类型相同,但在下方列出的调整后。
如果存在多个参数,则每个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 如果从 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 如果参数包作为最后一个 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
在推导开始之前,对P
和A
进行以下调整:
P
不是引用类型,A
是数组类型,则A
替换为从数组到指针转换获得的指针类型;A
是函数类型,则A
替换为从函数到指针转换获得的指针类型;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
P
是cv限定类型,则顶层cv限定符在推导中被忽略。P
是引用类型,则引用类型用于推导。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
)相同。
如果P
和A
的通常推导失败,则额外考虑以下替代方案:
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
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*)
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
的类型、模板和非类型值不参与模板实参推导,而是使用在其他地方推导或显式指定的模板实参。如果模板参数仅在非推导语境中使用且未显式指定,则模板实参推导失败。
// 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 起) |
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
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)
P
,其A
是函数或重载集,使得多于一个函数与P
匹配,或者没有函数与P
匹配,或者重载集包含一个或多个函数模板。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 起) |
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
,它依赖于一个或多个类型模板参数T
i、模板模板参数TT
i或非类型模板参数I
i,以及相应的实参A
,如果P
具有以下形式之一,则进行推导:
本节不完整 原因:可能是一个带微型示例的表格 |
-
cv
(可选)T
; -
T*
; -
T&
;
|
(C++11 起) |
-
T
(可选)[
I
(可选)]
;
|
(C++17 前) |
|
(C++17 起) |
-
T
(可选)U
(可选)::*
; -
TT
(可选)<T>
; -
TT
(可选)<I>
; -
TT
(可选)<TU>
; -
TT
(可选)<>
。
在上述形式中,
-
T
(可选) 或U
(可选) 表示一个类型或参数类型列表,它要么递归地满足这些规则,要么在P
或A
中是一个非推导语境,要么在P
和A
中是相同的非依赖类型。 -
TT
(可选) 或TU
(可选) 表示一个类模板或模板模板参数。 -
I
(可选) 表示一个表达式,它要么是一个I
,要么在P
或A
中是值依赖的,要么在P
和A
中具有相同的常量值。
|
(C++17 起) |
如果P
具有包含模板参数列表<T>
或<I>
的形式之一,则该模板实参列表的每个元素P
i都会与其A
的相应模板实参A
i匹配。如果最后一个P
i是包扩展,则其模式会与A
的模板实参列表中每个剩余实参进行比较。未被其他方式推导的尾随参数包被推导为空参数包。
如果P
具有包含函数参数列表(T)
的形式之一,则该列表中的每个参数P
i都会与A
的函数参数列表中相应的实参A
i进行比较。如果最后一个P
i是包扩展,则其声明符会与A
的参数类型列表中每个剩余的A
i进行比较。
形式可以嵌套并递归处理。
- X<int>(*)(char[6])是
T*
的一个例子,其中T
是X<int>(char[6]);
|
(C++17 前) |
|
(C++17 起) |
- X<int>是
TT
(可选)<T>
的一个例子,其中TT
是X
且T
是int,并且 - char[6]是
T
(可选)[
I
(可选)]
的一个例子,其中T
是char且I
是std::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>
template<class T, T i> void f(int (&a)[i]); int v[10]; f(v); // OK: T is std::size_t 函数类型中noexcept(B)说明符中 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 说明符的含义时,会使用模板实参推导。 参数 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返回函数,参数 auto f() { return 42; } // P = auto, A = 42: // deduced U = int, the return type of f is int 如果此类函数有多个return语句,则对每个return语句执行推导。所有结果类型必须相同并成为实际的返回类型。 如果此类函数没有return语句,则在推导时, 注意:变量和函数声明中decltype(auto)占位符的含义不使用模板实参推导。 |
(C++14 起) |
[编辑] 重载决议
在重载决议期间,从候选模板函数生成特化时会使用模板实参推导。P
和A
与常规函数调用中相同。
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
在这种情况下,推导会应用一条额外规则:当比较函数参数P
i和A
i时,如果任何P
i是对cv非限定模板参数的右值引用(“转发引用”),并且相应的A
i是左值引用,则将P
i调整为模板参数类型(T&&变为T)。
如果函数模板的返回类型是占位符(auto或decltype(auto)),则该返回类型是非推导语境,并从实例化中确定。 |
(C++14 起) |
[编辑] 偏序
在重载函数模板的偏序期间会使用模板实参推导。
本节不完整 原因:微型示例 |
[编辑] 转换函数模板
在选择用户定义转换函数模板实参时会使用模板实参推导。
A
是作为转换结果所需的类型。P
是转换函数模板的返回类型。如果P
是引用类型,则在以下部分中,引用类型用于代替P
。
如果A
不是引用类型:
P
是数组类型,则通过数组到指针转换获得的指针类型用于代替P
;P
是函数类型,则通过函数到指针转换获得的函数指针类型用于代替P
;P
是cv限定的,则顶层cv限定符被忽略。如果A
是cv限定的,则顶层cv限定符被忽略。如果A
是引用类型,则引用类型用于推导。
如果P
和A
的通常推导(如上所述)失败,则额外考虑以下替代方案:
A
是引用类型,则A
可以比推导出的A
具有更多的cv限定符;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
在这种情况下,推导会应用一条额外规则:当比较函数参数P
i和A
i时,如果任何P
i是对cv非限定模板参数的右值引用(“转发引用”),并且相应的A
i是左值引用,则将P
i调整为模板参数类型(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说明符中的值不可推导 | 变得可推导 |