Function template
函数模板定义了一系列函数。
目录 |
语法
template
<
parameter-list
>
function-declaration
|
(1) | ||||||||
template
<
parameter-list
>
requires
constraint
function-declaration
|
(2) | (C++20 起) | |||||||
| function-declaration-with-placeholders | (3) | (C++20 起) | |||||||
export
template
<
parameter-list
>
function-declaration
|
(4) | (C++11 中移除) | |||||||
说明
| 形参列表 | - | 一个非空的逗号分隔的 模板形参 列表,其中每个形参可以是 常量形参 、 类型形参 或 模板形参 ,或是上述任意类型的 形参包 (C++11 起) 。 与任何模板一样,形参可以被 约束 (C++20 起) |
| 函数声明 | - | 一个 函数声明 。声明的函数名将成为模板名。 |
| 约束 | - | 一个 约束表达式 ,用于限制此函数模板接受的模板形参 |
|
含占位符的
函数声明 |
- | 一个 函数声明 ,其中至少一个形参的类型使用了占位符 auto 或 Concept auto :模板形参列表将为每个占位符生成一个隐式声明的形参(参见下文简写函数模板) |
|
|
(直至 C++11) |
简写函数模板当占位符类型( auto 或 Concept auto )出现在函数声明或函数模板声明的参数列表中时,该声明会声明一个函数模板,并且每个占位符对应的模板参数会被追加到模板参数列表中: void f1(auto); // same as template<class T> void f1(T) void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&); template<class T, C U> void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z); 简写函数模板可以像所有函数模板一样进行特化。 template<> void f4<int>(const int*, const double&); // specialization of f4<int, const double>
|
(C++20 起) |
函数模板签名
每个函数模板都有一个签名。
模板头的签名是 模板参数列表 ,不包括模板参数名称和 默认参数 ,以及 requires 子句(如果存在) (C++20 起) 。
函数模板的签名包含名称、形参类型列表、返回类型 、尾随 requires 子句(如果存在) (C++20 起) 以及 template-head 的签名。除以下情况外,其签名还包含所在的命名空间。
若函数模板是类成员,其签名包含该函数所属的类而非外围命名空间。其签名还包含 后置 requires 子句(若存在) (C++20 起) 、引用限定符(若存在)与 (C++11 起) cv 限定符(若存在)。
|
若函数模板是包含外围模板参数的约束的 friend ,其签名将包含外围类而非外围命名空间。 |
(since C++20) |
函数模板实例化
函数模板本身不是类型,也不是函数。仅包含模板定义的源文件不会生成任何代码。为了生成实际代码,必须实例化模板:需要确定模板参数,以便编译器能够生成实际的函数(或从类模板生成类)。
显式实例化
template
返回类型
名称
<
参数列表
>
(
形参列表
)
;
|
(1) | ||||||||
template
返回类型
名称
(
形参列表
)
;
|
(2) | ||||||||
extern
template
返回类型
名称
<
参数列表
>
(
形参列表
)
;
|
(3) | (C++11 起) | |||||||
extern
template
返回类型
名称
(
形参列表
)
;
|
(4) | (C++11 起) | |||||||
显式实例化定义强制实例化它们所引用的函数或成员函数。它可以出现在模板定义之后的程序任意位置,并且对于给定的实参列表,只允许在程序中出现一次,不要求诊断信息。
|
显式实例化声明(extern模板)可阻止隐式实例化:原本会导致隐式实例化的代码必须使用程序中其他地方提供的显式实例化定义。 |
(since C++11) |
在函数模板特化或成员函数模板特化的显式实例化中,若尾随模板参数可通过 推导 从函数参数获得,则该参数可以省略不写:
template<typename T> void f(T s) { std::cout << s << '\n'; } template void f<double>(double); // 实例化 f<double>(double) template void f<>(char); // 实例化 f<char>(char),模板参数被推导 template void f(int); // 实例化 f<int>(int),模板参数被推导
函数模板或类模板成员函数的显式实例化不能使用
inline
或
constexpr
。如果显式实例化的声明指向隐式声明的特殊成员函数,则程序非良构。
构造函数的显式实例化不能使用模板参数列表(语法 (1) ),这也从无必要,因为模板参数可以被推导(语法 (2) )。
|
对 预期析构函数 的显式实例化必须指定类中被选定的析构函数。 |
(since C++20) |
显式实例化声明不会抑制内联函数、auto声明、引用和类模板特化的隐式实例化。(因此,当作为显式实例化声明主体的内联函数被ODR使用时,它会为内联而隐式实例化,但其外部副本不会在此翻译单元中生成)
带有 默认参数 的函数模板的显式实例化定义不是对参数的使用,也不会尝试初始化它们:
char* p = 0; template<class T> T g(T x = &p) { return x; } template int g<int>(int); // 即使 &p 不是 int 类型也是合法的
隐式实例化
当代码在需要 函数定义必须存在 的上下文中引用函数,或者定义的存在会影响程序语义 (自 C++11 起) ,且该特定函数尚未显式实例化时,会发生隐式实例化。如果模板参数可以从上下文 推导 ,则无需提供模板参数列表。
#include <iostream> template<typename T> void f(T s) { std::cout << s << '\n'; } int main() { f<double>(1); // 实例化并调用 f<double>(double) f<>('a'); // 实例化并调用 f<char>(char) f(7); // 实例化并调用 f<int>(int) void (*pf)(std::string) = f; // 实例化 f<string>(string) pf("∇"); // 调用 f<string>(string) }
|
若函数被表达式 需要用于常量求值 ,则函数定义的存在被视为会影响程序语义,即使表达式的常量求值并非必需,或常量表达式求值并未使用该定义。 template<typename T> constexpr int f() { return T::value; } template<bool B, typename T> void g(decltype(B ? f<T>() : 0)); template<bool B, typename T> void g(...); template<bool B, typename T> void h(decltype(int{B ? f<T>() : 0})); template<bool B, typename T> void h(...); void x() { g<false, int>(0); // OK: B ? f<T>() : 0 不是潜在常量求值表达式 h<false, int>(0); // 错误:即使B求值为false仍实例化f<int> // 且从int到int的列表初始化不能是窄化转换 } |
(C++11 起) |
注意:完全省略
<>
允许
overload resolution
同时检查模板和非模板重载。
模板实参推导
要实例化函数模板,必须已知所有模板参数,但并非所有模板参数都需要显式指定。在可能的情况下,编译器会从函数参数中推导缺失的模板参数。这种情况发生在尝试进行函数调用以及获取函数模板地址时。
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) }
这种机制使得使用模板运算符成为可能,因为除了将其重写为函数调用表达式外,没有其他语法可以指定运算符的模板参数。
模板实参推导发生在函数模板 名称查找 (可能涉及 实参依赖查找 )之后,并在 重载决议 之前进行。
详见 模板实参推导 。
显式模板参数
函数模板的模板参数可从以下途径获取:
- 模板实参推导
- 默认模板实参
- 显式指定,可在以下上下文中进行:
-
- 在函数调用表达式中
- 当获取函数地址时
- 当初始化函数引用时
- 当形成指向成员函数的指针时
- 在显式特化中
- 在显式实例化中
- 在友元声明中
无法显式指定模板参数给 重载运算符 、 转换函数 以及构造函数,因为这些函数在被调用时未使用函数名。
指定的模板实参必须在种类上匹配模板形参(即类型对类型、常量对常量、模板对模板)。实参数量不能超过形参数量 (除非某个形参是参数包,此时每个非包形参都必须有对应实参) (自 C++11 起) 。
指定的常量实参必须与对应常量模板参数的类型匹配,或 可转换为这些类型 。
不参与模板参数推导的函数参数(例如,若对应的模板参数已被显式指定)将进行隐式转换,转换为对应函数参数的类型(如同常规的 重载解析 中的规则)。
|
显式指定的模板参数包可通过模板实参推导进行扩展(当存在额外实参时): template<class... Types> void f(Types... values); void g() { f<int*, float*>(0, 0, 0); // Types = {int*, float*, int} } |
(C++11 起) |
模板实参替换
当所有模板参数都已指定、推导或从默认模板参数中获取后,函数参数列表中每个模板参数的使用都会被对应的模板实参替换。
函数模板的替换失败(即无法用推导或提供的模板参数替换模板参数)会将该函数模板从 重载集 中移除。这为使用模板元编程操作重载集提供了多种方式:详见 SFINAE 说明。
替换后,所有数组和函数类型的函数参数都会被调整为指针,并且所有顶层 cv 限定符会从函数参数中移除(如同常规的 函数声明 中那样)。
顶层 cv 限定符的移除不会影响参数在函数内部表现出的类型:
template<class T> void f(T t); template<class X> void g(const X x); template<class Z> void h(Z z, Z* zp); // 两个具有相同类型但不同cv限定符的函数 // 在函数内部,t具有不同的cv限定 f<int>(1); // 函数类型为void(int),t的类型为int f<const int>(1); // 函数类型为void(int),t的类型为const int // 两个具有相同类型和相同x的函数 //(这两个函数的指针不相等, // 函数局部静态变量将具有不同地址) g<int>(1); // 函数类型为void(int),x的类型为const int g<const int>(1); // 函数类型为void(int),x的类型为const int // 仅顶层cv限定符被丢弃: h<const int>(1, NULL); // 函数类型为void(int, const int*) // z的类型为const int,zp的类型为const int*
函数模板重载
函数模板和非模板函数可以被重载。
非模板函数总是与具有相同类型的模板特化不同。不同函数模板的特化即使具有相同类型也总是彼此不同。两个具有相同返回类型和相同参数列表的函数模板是不同的,可以通过它们的显式模板实参列表进行区分。
当使用类型或常量模板参数的表达式出现在函数参数列表或返回类型中时,该表达式仍作为函数模板签名的一部分用于重载目的:
template<int I, int J> A<I+J> f(A<I>, A<J>); // 重载 #1 template<int K, int L> A<K+L> f(A<K>, A<L>); // 与 #1 相同 template<int I, int J> A<I-J> f(A<I>, A<J>); // 重载 #2
涉及模板参数的两个表达式被称为 等价 的,如果包含这些表达式的两个函数定义在 ODR 规则下是相同的,即这两个表达式包含相同的记号序列,且这些记号的名称通过名称查找解析为相同的实体,但模板参数可以具有不同的名称。 两个 lambda表达式 永远不是等价的。 (C++20 起)
template<int I, int J> void f(A<I+J>); // 模板重载 #1 template<int K, int L> void f(A<K+L>); // 与 #1 等价
在判断两个 dependent expressions 是否等价时,仅考虑涉及的依赖名称,而不考虑名称查找的结果。如果同一模板的多个声明在名称查找结果上存在差异,则使用第一个此类声明:
template<class T> decltype(g(T())) h(); // decltype(g(T())) 是依赖类型 int g(int); template<class T> decltype(g(T())) h() { // h() 的重声明使用先前的查找结果 return g(T()); // 尽管此处的查找确实能找到 g(int) } int i = h<int>(); // 模板实参替换失败;g(int) // 在 h() 的首次声明时不在作用域内
两个函数模板在以下情况下被视为 等价 :
- 它们声明在同一作用域内
- 它们具有相同的名称
- 它们具有 等效的 模板参数列表,即列表长度相同,且对于每个对应的参数对,以下所有条件均成立:
-
- 两个参数属于相同类别(同为类型、同为常量或同为模板)
|
(since C++11) |
-
- 若为常量,则它们的类型是等价的,
- 若为模板,则它们的模板参数是等价的,
|
(自 C++20 起) |
- 其返回类型和参数列表中涉及模板参数的表达式是 等价的
|
(C++20 起) |
两个涉及模板参数的 可能被求值 (since C++20) 表达式被称为 功能等价 ,如果它们不是 等价 的,但对于任意给定的模板参数集,这两个表达式的求值结果相同。
两个函数模板被认为是 功能等效的 ,当它们 等价 ,除了在其返回类型和参数列表中涉及模板参数的一个或多个表达式是 功能等效的 。
|
此外,若两个函数模板的约束条件指定方式不同,但接受并满足相同的模板实参列表集合,则它们属于 功能等效 但非 等效 关系。 |
(since C++20) |
如果程序包含功能等效但非等效的函数模板声明,则该程序格式错误;无需提供诊断信息。
// 等价声明 template<int I> void f(A<I>, A<I+10>); // 重载 #1 template<int I> void f(A<I>, A<I+10>); // 重载 #1 的重复声明 // 不等价声明 template<int I> void f(A<I>, A<I+10>); // 重载 #1 template<int I> void f(A<I>, A<I+11>); // 重载 #2 // 功能等价但不等价 // 此程序非良构,不要求诊断 template<int I> void f(A<I>, A<I+10>); // 重载 #1 template<int I> void f(A<I>, A<I+1+2+3+4>); // 功能等价
当同一函数模板特化匹配多个重载函数模板时(这通常源于 模板实参推导 ), 重载函数模板的偏序规则 会被执行以选择最佳匹配。
具体而言,在以下情况下会发生偏序:
template<class X> void f(X a); template<class X> void f(X* a); void (*p)(int*) = &f;
template<class X> void f(X a); // 第一个模板 f template<class X> void f(X* a); // 第二个模板 f template<> void f<>(int *a) {} // 显式特化 // 模板实参推导产生两个候选: // f<int*>(int*) 和 f<int>(int*) // 偏序规则选择 f<int>(int*) 作为更特化的版本
非正式地说,“A 比 B 更特化”意味着“A 接受的类型比 B 更少”。
正式而言,为确定任意两个函数模板中哪个更特化,偏序处理首先按以下方式转换其中一个模板:
- 对于每个类型、常量和模板参数, 包括参数包, (C++11 起) 都会生成唯一的虚构类型、值或模板,并代入模板的函数类型中
-
若两个被比较的函数模板中仅有一个是成员函数,且该函数模板是某个类
A的非静态成员,则会在其参数列表首插入一个新参数。给定 cv 作为函数模板的 cv 限定符 及 ref 作为函数模板的引用限定符 (C++11 起) ,新参数类型为 cvA&除非 ref 为&&,或 ref 不存在且另一模板的首参数具有右值引用类型,此时类型为 cvA&&(C++11 起) 。这有助于运算符的排序,因为运算符同时作为成员函数和非成员函数被查找:
struct A {}; template<class T> struct B { template<class R> int operator*(R&); // #1 }; template<class T, class R> int operator*(T&, R&); // #2 int main() { A a; B<A> b; b * a; // 对 int B<A>::operator*(R&) 进行模板实参推导得到 R=A // 对 int operator*(T&, R&) 进行推导得到 T=B<A>, R=A // 出于偏序排序的目的,成员模板 B<A>::operator* // 被转换为 template<class R> int operator*(B<A>&, R&); // 在以下两者之间进行偏序排序: // int operator*( T&, R&) T=B<A>, R=A // 与 int operator*(B<A>&, R&) R=A // 选择 int operator*(B<A>&, A&) 作为更特化的版本 }
当两个模板之一按照上述方式转换后, 模板实参推导 将使用转换后的模板作为实参模板,另一模板的原始模板类型作为形参模板来执行。随后使用第二个模板(转换后)作为实参,第一个模板的原始形式作为形参,重复此过程。
用于确定顺序的类型取决于上下文:
- 在函数调用语境中,类型是指函数调用实参所对应的函数形参类型(默认函数实参、 形参包、 (C++11 起) 以及省略号形参不予考虑——详见下方示例)
- 在调用用户定义转换函数的语境中,使用转换函数模板的返回类型
- 在其他语境中,使用函数模板类型
参数模板中的上述列表中的每个类型都会被推导。在推导开始之前,参数模板的每个参数
P
及其对应的实参模板中的实参
A
会进行如下调整:
-
如果之前
P和A都是引用类型,则判定哪个具有更多的cv限定符(其他情况下,cv限定符在偏序处理中会被忽略) -
如果
P是引用类型,则将其替换为所引用的类型 -
如果
A是引用类型,则将其替换为所引用的类型 -
如果
P带有cv限定符,则将P替换为其无cv限定的版本 -
如果
A带有cv限定符,则将A替换为其无cv限定的版本
经过这些调整后,从
A
中推导
P
的过程遵循
从类型进行的模板实参推导
。
|
若
若
|
(C++11 起) |
如果转换后的模板-1的实参
A
可用于推导模板-2的对应形参
P
,但反之不成立,则就由此
P/A
对所推导的类型而言,此
A
比
P
更特化。
如果两个方向的推导都成功,且原始的
P
和
A
是引用类型,则会进行额外测试:
-
如果
A是左值引用且P是右值引用,A被视为比P更特化 -
如果
A比P具有更多cv限定符,A被视为比P更特化
在所有其他情况下,对于通过此
P/A
对推导出的类型,两个模板都不比另一个更特化。
在双向考量了每个
P
和
A
之后,如果对于每个被考量的类型,
- 模板1对所有类型至少与模板2一样特化
- 模板1对某些类型比模板2更特化
- 模板2对任何类型都不比模板1更特化,或者对任何类型都不至少同样特化
那么模板-1比模板-2更特化。如果调换模板顺序后上述条件仍然成立,则模板-2比模板-1更特化。否则,两个模板之间不存在更特化关系。
|
若出现平局,且一个函数模板具有尾部形参包而另一个没有,则省略形参的模板被视为比具有空形参包的模板更特化。 |
(C++11 起) |
如果在考虑所有重载模板对之后,存在一个模板明确比其他所有模板更特化,则选择该模板的特化版本,否则编译失败。
在以下示例中,虚构参数将被称为 U1、U2:
template<class T> void f(T); // 模板 #1 template<class T> void f(T*); // 模板 #2 template<class T> void f(const T*); // 模板 #3 void m() { const int* p; f(p); // 重载决议选择: #1: void f(T ) [T = const int *] // #2: void f(T*) [T = const int] // #3: void f(const T *) [T = int] // 偏序规则: // #1 从转换后的 #2:void(T) 来自 void(U1*):P=T A=U1*:推导成功:T=U1* // #2 从转换后的 #1:void(T*) 来自 void(U1):P=T* A=U1:推导失败 // 对于 T 而言,#2 比 #1 更特化 // #1 从转换后的 #3:void(T) 来自 void(const U1*):P=T, A=const U1*:成功 // #3 从转换后的 #1:void(const T*) 来自 void(U1):P=const T*, A=U1:失败 // 对于 T 而言,#3 比 #1 更特化 // #2 从转换后的 #3:void(T*) 来自 void(const U1*):P=T* A=const U1*:成功 // #3 从转换后的 #2:void(const T*) 来自 void(U1*):P=const T* A=U1*:失败 // 对于 T 而言,#3 比 #2 更特化 // 结果:选择 #3 // 换言之,f(const T*) 比 f(T) 或 f(T*) 更特化 }
template<class T> void f(T, T*); // #1 template<class T> void f(T, int*); // #2 void m(int* p) { f(0, p); // 对 #1 的推导:void f(T, T*) [T = int] // 对 #2 的推导:void f(T, int*) [T = int] // 偏序处理: // #1 从 #2 推导:void(T,T*) 从 void(U1,int*) 推导:P1=T, A1=U1: T=U1 // P2=T*, A2=int*: T=int: 失败 // #2 从 #1 推导:void(T,int*) 从 void(U1,U2*) 推导:P1=T A1=U1: T=U1 // P2=int* A2=U2*: 失败 // 两者在 T 参数上均不具备更高特化程度,调用存在二义性 }
template<class T> void g(T); // 模板 #1 template<class T> void g(T&); // 模板 #2 void m() { float x; g(x); // 从 #1 推导:void g(T ) [T = float] // 从 #2 推导:void g(T&) [T = float] // 偏序规则: // #1 与 #2 比较:void(T) 与 void(U1&):P=T,A=U1(调整后),匹配成功 // #2 与 #1 比较:void(T&) 与 void(U1):P=T(调整后),A=U1:匹配成功 // 两者在 T 上均未更特化,调用存在歧义 }
template<class T> struct A { A(); }; template<class T> void h(const T&); // #1 template<class T> void h(A<T>&); // #2 void m() { A<int> z; h(z); // 从 #1 推导:void h(const T &) [T = A<int>] // 从 #2 推导:void h(A<T> &) [T = int] // 偏序规则: // #1 与 #2:void(const T&) 与 void(A<U1>&): P=T A=A<U1>: 成功 T=A<U1> // #2 与 #1:void(A<T>&) 与 void(const U1&): P=A<T> A=const U1: 失败 // 对于 T 而言,#2 比 #1 更特化 const A<int> z2; h(z2); // 从 #1 推导:void h(const T&) [T = A<int>] // 从 #2 推导:void h(A<T>&) [T = int],但替换失败 // 仅有一个重载可选,不尝试偏序排序,调用 #1 }
由于调用上下文仅考虑具有显式调用实参的参数,那些 函数形参包、 (C++11 起) 省略号形参以及具有默认实参但未提供显式调用实参的形参将被忽略:
template<class T> void f(T); // #1 template<class T> void f(T*, int = 1); // #2 void m(int* ip) { int* ip; f(ip); // 调用 #2(T* 比 T 更特化) }
template<class T> void g(T); // #1 template<class T> void g(T*, ...); // #2 void m(int* ip) { g(ip); // 调用 #2(T* 比 T 更特化) }
template<class T, class U> struct A {}; template<class T, class U> void f(U, A<U, T>* p = 0); // #1 template<class U> void f(U, A<U, U>* p = 0); // #2 void h() { f<int>(42, (A<int, int>*)0); // 调用 #2 f<int>(42); // 错误:存在歧义 }
template<class T> void g(T, T = T()); // #1 template<class T, class... U> void g(T, U...); // #2 void h() { g(42); // 错误:存在歧义 }
template<class T, class... U> void f(T, U...); // #1 template<class T> void f(T); // #2 void h(int i) { f(&i); // 调用 #2,因为参数包和无参数之间的决胜规则 // (注意:在 DR692 和 DR1395 之间曾存在歧义) }
template<class T, class... U> void g(T*, U...); // #1 template<class T> void g(T); // #2 void h(int i) { g(&i); // 正确:调用 #1(T* 比 T 更特化) }
template<class... T> int f(T*...); // #1 template<class T> int f(const T&); // #2 f((int*)0); // 正确:选择 #2;非可变参数模板比可变参数模板更特化 // (在 DR1395 之前存在歧义,因为双向推导均失败)
template<class... Args> void f(Args... args); // #1 template<class T1, class... Args> void f(T1 a1, Args... args); // #2 template<class T1, class T2> void f(T1 a1, T2 a2); // #3 f(); // 调用 #1 f(1, 2, 3); // 调用 #2 f(1, 2); // 调用 #3;非可变参数模板 #3 比可变参数模板 #1 和 #2 更特化 //
在部分排序过程中的模板实参推导期间,如果实参未被用于部分排序所考虑的任何类型中,则模板形参无需与实参匹配
template<class T> T f(int); // #1 template<class T, class U> T f(U); // #2 void g() { f<int>(1); // #1 的显式特化:T f(int) [T = int] // #2 的推导特化:T f(U) [T = int, U = int] // 偏序规则(仅考虑参数类型): // #1 与 #2 匹配:T(int) 与 U1(U2) 匹配:失败 // #2 与 #1 匹配:T(U) 与 U1(int) 匹配:成功 U=int, T 未使用 // 调用 #1 }
|
包含模板参数包的函数模板的偏序规则与这些模板参数包的推导参数数量无关。 template<class...> struct Tuple {}; template<class... Types> void g(Tuple<Types...>); // #1 template<class T1, class... Types> void g(Tuple<T1, Types...>); // #2 template<class T1, class... Types> void g(Tuple<T1, Types&...>); // #3 g(Tuple<>()); // calls #1 g(Tuple<int, float>()); // calls #2 g(Tuple<int, float&>()); // calls #3 g(Tuple<int>()); // calls #3 |
(C++11 起) |
|
本节内容不完整
原因:14.8.3[temp.over] |
要编译对函数模板的调用,编译器必须在非模板重载、模板重载以及模板重载的特化之间做出决定。
template<class T> void f(T); // #1: 模板重载 template<class T> void f(T*); // #2: 模板重载 void f(double); // #3: 非模板重载 template<> void f(int); // #4: #1 的特化 f('a'); // 调用 #1 f(new int(1)); // 调用 #2 f(1.0); // 调用 #3 f(1); // 调用 #4
函数重载与函数特化
请注意,只有非模板和主模板的重载参与重载决议。特化不是重载,也不会被考虑。只有在重载决议选择了最佳匹配的主函数模板后,才会检查其特化版本是否具有更优匹配。
template<class T> void f(T); // #1:针对所有类型的重载 template<> void f(int*); // #2:针对int指针的#1特化版本 template<class T> void f(T*); // #3:针对所有指针类型的重载 f(new int(1)); // 调用#3,即使#1的特化版本是完全匹配
在安排翻译单元的头文件顺序时,务必牢记此规则。如需查看更多函数重载与函数特化相互作用的示例,请展开下方内容:
| 示例 |
|---|
|
首先考虑一些不使用实参依赖查找的场景。为此,我们使用调用 ( f ) ( t ) 。如 ADL 中所述,将函数名包裹在圆括号中会抑制实参依赖查找。
运行此代码
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // f() POR 前的重载 #1 template<class T> void f(T*) { std::cout << "#2\n"; } // f() POR 前的重载 #2 template<class T> void g(T* t) { (f)(t); // f() POR } int main() { A* p = nullptr; g(p); // g() 和 f() 的 POR } // #1 和 #2 均被加入候选列表; // 由于 #2 是更优匹配,因此被选中。 输出: #2
运行此代码
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // POR of g() and f() } // Only #1 is added to the candidate list; #2 is defined after POR; // therefore, it is not considered for overloading even if it is a better match. 输出: #1
运行此代码
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() POR } template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 被加入候选列表;#3 是在 POR 之后定义的更优匹配。 // 候选列表最初由 #1 组成并最终被选中。之后,在 POI 之后声明的 #1 的显式特化 #3 因其更优匹配而被选中。 // 此行为由 14.7.3/6 [temp.expl.spec] 规定,与 ADL 无关。 输出: #3
运行此代码
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() 的实例化点 } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // g() 和 f() 的实例化点 } // #1 是候选列表中唯一的成员,最终被选中。 // 之后,显式特化 #3 被跳过,因为它实际上特化的是在实例化点之后声明的 #2。 输出: #1
运行此代码
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() 的实例化点 } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // g() 和 f() 的实例化点 } // #1 通过普通查找被加入候选列表; // #2 在实例化点之后定义,但通过 ADL 查找加入候选列表。 // #2 因更匹配而被选中。 输出: #2
运行此代码
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() POR } template<> void f<>(A*) { std::cout << "#3\n"; } // #3 template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is added to the candidate list as a result of the ordinary lookup; // #2 is defined after POR but it is added to the candidate list via ADL lookup. // #2 is selected among the primary templates, being the better match. // Since #3 is declared before #2, it is an explicit specialization of #1. // Hence the final selection is #2. 输出: #2
运行此代码
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() 的实例化点 } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // g() 和 f() 的实例化点 } // #1 通过普通查找加入候选列表; // #2 在实例化点之后定义,但通过 ADL 查找加入候选列表 // #2 在主模板中被选为更优匹配 // 由于 #3 在 #2 之后声明,它是 #2 的显式特化版本; // 因此被选为最终调用的函数 输出: #3
|
有关重载解析的详细规则,请参阅 重载解析 。
函数模板特化
|
本节内容不完整
原因:14.8[temp.fct.spec](注意14.8.1[temp.arg.explicit]已包含在完全特化条目中:此处应补充函数特化的具体内容——包括偏特化的缺失、与函数重载的交互关系,或直接引用该章节) |
关键词
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 应用于 | 已发布的行为 | 正确行为 |
|---|---|---|---|
| CWG 214 | C++98 | 未具体说明偏序化的确切流程 | 已添加规范说明 |
| CWG 532 | C++98 |
未指定非静态成员函数模板
与非成员函数模板之间的顺序 |
已添加规范说明 |
| CWG 581 | C++98 |
允许在构造函数模板的显式特化
或实例化中使用模板实参列表 |
已禁止 |
| CWG 1321 | C++98 |
首次声明和重新声明中的相同依赖名称
是否等价不明确 |
它们等价且含义
与首次声明中 相同 |
| CWG 1395 | C++11 |
当A来自参数包且不存在
空包决胜规则时推导失败 |
允许推导
已添加决胜规则 |
| CWG 1406 | C++11 |
为非静态成员函数模板新增的
首个参数类型与其引用限定符 无关 |
当引用限定符为
&&
时
该类型为右值引用类型 |
| CWG 1446 | C++11 |
无引用限定符的非静态成员函数模板
新增的首个参数类型为左值引用类型 即使该成员函数模板与首个参数为 右值引用类型的函数模板进行比较 |
在此情况下
该类型为右值 引用类型 |
| CWG 2373 | C++98 |
在偏序化中为静态成员函数模板的
参数列表添加了新的首个参数 |
不再添加 |