函数模板

来自cppreference.com
< cpp‎ | language

函数模板定义一族函数。

目录

[编辑] 语法

template < parameter-list > function-declaration (1)
template < parameter-list > requires constraint function-declaration (2) (C++20 起)
function-declaration-with-placeholders (3) (概念 TS)
export template < parameter-list > function-declaration (4) (C++11 前)

[编辑] 解释

parameter-list - 模板形参的非空逗号分隔列表,每项是非类型形参类型形参模板形参或这些的参数包之一。同任何模板,形参可以有制约 (C++20 起)
function-declaration - 函数声明。声明的函数名成为模板名。
constraint(C++20) - 制约表达式,它限制此函数模板接受的模板形参。
function-declaration-with-placeholders(概念 TS) - 函数声明,其中至少一个参数用占位符 auto有制约类型指定符,模板形参列表将对每个占位符拥有一个虚设形参。
export 是一个可选的修饰符,声明模板为被导出(用于类模板时,它声明所有成员为导出)。带有实例化被导出模板的文件不需要包含其定义:声明就足够了。 export 的实现稀少而且在细节上互不一致。 (C++11 前)

占位符( auto有制约占位符)出现于函数声明或函数模板声明的参数列表时,对每个占位符添加一个虚设模板形参到模板形参列表

void f(auto (auto::*)(auto)); // #1
template<typename T, typename U, typename V> void f(T (U::*)(V)); // 同 #1
 
template<int N> void f(Array<auto, N>*); // #2 (假设 Array 是类模板)
template<int N, typename T> void f(Array<T, N>*); // 同 #2
 
void g1(const C1*, C2&); // #3 (假设 C1 与 C2 是概念)
template<C1 T, C2 U> void g1(const T*, U&); // 同 #3
(概念 TS)

[编辑] 函数模板实例化

函数模板自身不是类型或函数或任何实体。从仅包含模板定义的源文件不生成代码。为使得代码出现,必须实例化模板:必须确定模板实参,以令编译器能生成实际函数(或从类模板生成类)。

[编辑] 显式实例化

template return-type name < argument-list > ( parameter-list ) ; (1)
template return-type name ( parameter-list ) ; (2)
extern template return-type name < argument-list > ( parameter-list ) ; (3) (C++11 起)
extern template return-type name ( parameter-list ) ; (4) (C++11 起)
1) 显式实例化定义(若显式指定每个非默认模板形参,则无模板实参推导
2) 显式实例化定义,有对所有形参的模板实参推导
3) 显示实例化声明(若显式指定每个非默认模板形参,则无模板实参推导)
4) 显示实例化声明,有对所有形参的模板实参推导

显式实例化定义强制它们所指代的函数或成员函数实例化。它可出现于程序中模板定义后的任何位置,而对于给定的实参列表,程序中只允许出现一次。

显式实例化声明( extern 模板)阻止隐式实例化:其余情况下会导致隐式实例化的代码,必须使用已在程序的某个其他位置提供的显式实例化。


函数模板特化或成员函数模板特化的显式实例化中,尾随的模板实参可以保留未指定,若它能从函数参数推导

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) ,推导出模板实参

函数模板或类模板成员函数的显式实例化不能使用 inlineconstexpr 。若显式实例化的声明指名隐式声明的特殊成员函数,则程序为病式。

显式实例化声明不抑制 inline 函数、 auto 声明、引用及类模板特化的隐式实例化。(从而,在 ODR 使用作为显式实例化声明目标的 inline 函数时,函数为内联而隐式实例化,但此翻译单元中不生成其非内联副本)

默认参数的函数模板的显式实例化定义不使用该参数,且不会试图实例化之:

char* p = 0;
template<class T> T g(T x = &p) { return x; }
template int g<int>(int);   // OK 即使 &p 不是 int 。

[编辑] 隐式实例化

代码在要求函数定义存在的语境中指涉函数,且此特定函数未被显式实例化时,隐式实例化发生。若模板实参列表能从语境推导,则不必提供它。

#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 (*ptr)(std::string) = f; // 实例化 f<string>(string)
}


注意:完全省略 <> 会允许重载决议检验模板与非模板重载。

[编辑] 模板实参推导

为实例化函数模板,必须知道每个模板实参,但并非必须指定每个模板实参。在可能时,编译器会从函数实参推导缺失的模板实参。这发生于尝试函数调用时及取函数模板的地址时。

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

模板实参推导发生后于函数模板名称查找(可能涉及参数依赖查找),先于重载决议

细节见模板实参推导

[编辑] 显式模板实参

函数模板的模板实参可从下列内容获得

  • 模板实参推导
  • 默认模板实参
  • 显式指定,可以在下列语境中进行:
  • 于函数调用表达式
  • 取函数地址时
  • 初始化到函数的引用时
  • 形成指向成员函数指针时
  • 于显式特化
  • 于显式实例化
  • 于友元声明

没有显式指定模板实参给重载的运算符转换函数及构造函数的方法,因为它们以不使用函数名的方式调用。

指定的模板实参必须匹配模板形参列表的种类(即类型对类型,非类型对非类型和模板对模板)。不能有多于形参数量的实参(除非形参之一是形参包,该情况下对每个非包形参必须有一个实参)。

指定的非类型实参必须匹配对应的非类型模板形参类型,或可转换成它们

不参与模板实参推导的函数参数(例如若已显式指定对应的模板事故差别)是到对应函数参数类型的隐式转换的目标(如在通常重载决议中)。

显式指定的模板参数包可以为模板实参推导扩充,若有额外的实参:

template<class ... Types> void f(Types ... values);
void g() {
  f<int*, float*>(0, 0, 0); // Types = {int*, float*, int}
}

[编辑] 模板实参替换

已指定、推导出或从默认模板实参获得所有模板实参时,函数参数列表中每次模板形参的使用都会被替换成对应的模板实参。

函数模板的替换失败(即以推导或提供的模板实参替换模板形参失败)会将函数模板从重载集中移除。这允许多种使用模板元编程的方法制备重载集:细节见 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);
 
// 二个不同函数有同一类型,但在函数中, 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 为 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

确定二个依赖表达式是否等价时,只考虑涉及的依赖名,而不考虑名称查找的结果。若名称查找的结果中同一模板的多个定义相异,则使用首个这种声明:

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() 首个声明所在的作用域
(C++14 起)

若满足下列条件,则认为二个函数模板等价

  • 它们声明于同一作用域
  • 它们有相同名称
  • 它们有等同的模板形参列表
  • 其返回类型和参数列表中涉及模板实参的表达式等价
  • 其后随模板形参列表的 requires 子句中的表达式等价,若它们存在
  • 其后随函数声明器的 requires 子句中的表达式等价,若它们存在
(C++20 起)

若二个涉及模板形参的表达式不等价,但对于任何给定的模板实参集,二个表达式的求值都产生相同值,则称它们功能等价

若二个函数模板本可等价,除了其返回类型和参数列表中涉及模板形参的一或多个表达式功能等价,则称它们功能等价

另外,若二个函数模板的制约有别,但它们接受而且满足于同一模板实参列表集合,则它们功能等价但不等价

(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>); // 功能等价

同一函数模板特化匹配多于一个重载的函数模板(这经常产生自模板实参推导),则进行重载函数模板偏排序以选择最佳匹配。

具体而言,偏排序发生于下列情形:

1) 调用到函数模板特化的重载决议
template<class X> void f(X a);
template<class X> void f(X* a);
int* p;
f(p);
2)函数模板特化的地址
template<class X> void f(X a);
template<class X> void f(X* a);
void (*p)(int*) = &f;
3) 选择作为函数模板特化的布置 operator delete 匹配布置 operator new 时
4) 友元函数声明显式实例化显式特化指代函数模板特化时
template<class X> void f(X a);  // 第一个 template f
template<class X> void f(X* a); // 第二个 template f
template<> void f<>(int *a) {} // 显式特化
 // 模板实参推导出现二个候选:
 // foo<int*>(int*) 与 f<int>(int*)
 // 偏序选择 f<int>(int*) ,因为它更特化

非正式而言,“ A 比 B 更特化”表示“ A 接受少于 B 的类型”。

正式而言,为确定二个函数模板中何者更为特化,偏序处理首先以下列方式变形二个模板之一:

  • 对于每个类型、非类型及模板形参,包含形参包,生成唯一的虚构类型、值或模板,以之替换模板的函数类型
  • 若要比较的二个函数模板只有一个是某类 A 的非静态成员,则插入新的首形参到其参数列表,若成员函数模板为 && 限定,则形参类型为 cv A&& ,否则为 cv A& ( cv 是成员函数模板的 cv 限定)——这有助于同时作为成员与非成员函数查找的运算符的排序:
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
// 为偏序目的,成员 template 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&)

在按上方描述变形二个模板之一后,以变形的模板为实参模板,以另一模板的原模板类型为形参模板,执行模板实参推导。重复以第二模板(变形后)为实参,用其原型的第一模板为形参处理。

用于确定顺序的类型依赖于语境:

  • 在函数调用语境,类型是该函数调用中实参对应的函数形参类型(不考虑默认函数参数、参数包及省略号参数——见后述)
  • 在用户定义转换函数调用语境,使用转换函数模板的返回类型
  • 在其他语境,使用函数模板类型

从来自形参模板的上述列表推导每个类型。推导开始前,以下列方式调整形参模板的每个形参 P 和实参模板的每个实参 A

  • PA 均为引用类型,则确定何者更为 cv 限定(所有其他情况下,都为偏序目的忽略 cv 限定)
  • P 是引用类型,则以其所引用的类型替换它
  • A 是引用类型,则以其所引用的类型替换它
  • P 有 cv 限定,则 P 被替换为自身的无 cv 限定版本
  • A 有 cv 限定,则 A 被替换为自身的无 cv 限定版本

在这些调整后,遵循从类型的模板实参推导进行 PA 的推导。

若 P 是函数参数包,则比较实参模板的每个剩余形参类型的类型 A 与函数参数包的每个声明器 id 的类型 P 。每个比较从函数参数包所展开的模板参数包中的后继位置推导模板实参。

若 A 变形自函数参数包,则推导失败。 (C++14 前)将它与形参模板的每个剩余形参类型比较。 (C++14 起)

若被变换模板 1 的实参 A 能用来推导对应的模板 2 的形参 P ,但反之不可,则此 AP 更为特化,对于从此 P/A 对推导的类型而言。

若双向推导均成功,且原 PA 是引用类型,则做附加的测试:

  • A 是左值引用而 P 是右值引用,则认为 A 比 P 更为特化
  • AP 更有 cv 限定,则认为 A 比 P 更为特化

所有其他情况下,对于 P/A 对所推导的类型,无一模板比另一者更特化。

在以双向考虑每个 P 与 A 后,若对于考虑的每个类型,

  • 模板 1 对所有类型至少与模板 2 一样特化
  • 模板 1 对某些类型比模板 2 特化
  • 模板 2 对任何类型都不比模板 1 更特化,或,对任何类型不至少一样特化

则模板 1 比模板 2 更特化。若上述条件在切换模板顺序后为真,则模板 2 比模板 1 更特化。否则,无一模板并另一者更特化。 持平的情况下,若一个函数模板有尾随参数包而另一个没有,则认为有被省略形参者比有空参数包者更为特化。

若在考虑所有重载的模板对后,有一个无歧义地比所有其他模板都更为特化,则该模板的特化得到选择,否则编译失败。

在下列示例中,虚构实参将被称为 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* :推导 ok : 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* : ok
// #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* : ok
// #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 (调整后): ok
// #2 从 #1 : void(T&) 从 void(U1) : P=T (调整后) A=U1 : ok
// 对于 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> : ok 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
}

因为在调用语境中只考虑有显式调用实参的形参,故无显式调用实参的函数参数包、省略号形参及有默认参数的形参被忽略:

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);        // OK :调用 #1 ( T* 比 T 更特化)
}
template <class ...T> int f(T*...);  // #1
template <class T>  int f(const T&); // #2
f((int*)0); // OK :选择 #1
            // ( 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) : ok : 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<>());                     // 调用 #1
g(Tuple<int, float>());           // 调用 #2
g(Tuple<int, float&>());          // 调用 #3
g(Tuple<int>());                  // 调用 #3


为编译到函数模板的调用,编译器必须在非模板重载、模板重载和模板重载的特化间决定。

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

[编辑] 函数重载 vs 模板特化

注意只有非模板和初等模板重载参与重载决议。特化不是重载,且不受考虑。只有在重载决议选择最佳匹配初等函数模板后,才检验其特化以查看何为最佳匹配。

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 的情况,将函数名包在括号中抑制参数依赖查找。

  • 声明于 g()引用点( POR )前的多个 f() 重载。
#include <iostream>
struct A{};
template<class T> void f(T)    {std::cout << "#1\n";} // 重载 #1 在 f() POR 前
template<class T> void f(T*)   {std::cout << "#2\n";} // 重载 #2 在 f() POR 前
template<class T> void g(T* t) 
{
    (f)(t); // f() POR
}
 
int main()
{
    A *p=nullptr;
    g(p);
}           // POI of g() and f()
 
// #1 与 #2 都被添加到候选列表;
// 选择 #2 因为它是更好的匹配。

输出:

#2


  • 较好的匹配模板重载声明于 POR 后。
#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);
}           // POI of g() and f()
 
// 仅添加 #1 到候选列表; #2 定义后于 POR ;
// 从而它不为重载所考虑,即使它是较佳匹配。

输出:

#1


  • 较好的显式模板特化声明于 POR 后。
#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);
}           // g() 与 f() 的 POI
 
// 添加 #1 到候选列表; #3 是定义于 POR 后的更好匹配。候选列表由最终选择的 #1 组成,之后,
// 选择声明于 POI 后的 #1 的显式特化 #3 ,因为它是较好匹配。
// 此行为由 14.7.3/6 [temp.expl.spec] 掌控且无需处理 ADL 。

输出:

#3


  • 较好模板重载声明于 POR 后。最佳匹配显式模板特化声明于更好的匹配重载之后。
#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
template<>        void f<>(A*) {std::cout << "#3\n";} // #3
 
int main()
{
    A *p=nullptr;
    g(p);
}           // g() 与 f() 的 POI
 
// #1 是候选列表的唯一成员且它被最终选择。
// 之后,跳过显式特化 #3 ,因为它实际特化声明于 POR 后的 #2 。

输出:

#1


现在让我们考虑使用参数依赖查找(即我们用更常见的调用格式 f(t) )。

  • 更好的匹配模板重载声明于 POR 后。
#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);
}           // g() 与 f() 的 POI
 
// #1 被作为通常名称查找的结果添加到候选列表;
// #2 定义于 POR 后但经由 ADL 查找添加到候选列表。
// #2 被选为更好的匹配。

输出:

#2


  • 较好的匹配模板重载声明于 POR 后。最佳匹配显式模板实例化声明于较好匹配重载前。
#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);
}           // g() 与 f() 的 POI
 
// #1 被作为通常名称查找的结果添加到候选列表;
// #2 定义于 POR 后但经由 ADL 查找添加到候选列表。
// #2 在初等模板中得到选择,作为较好匹配。
// 因为 #3 声明先于 #2 ,故它是 #1 的显式特化。
// 从而最终选择是 #2 。

输出:

#2


  • 较好的匹配模板重载声明后于 POR ,最佳匹配显式模板特化最后声明。
#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
template<>        void f<>(A*) {std::cout << "#3\n";} // #3
 
int main()
{
    A *p=nullptr;
    g(p);
}           // g() 与 f() 的 POI
 
// #1 被作为通常名称查找的结果添加到候选列表;
// #2 定义于 POR 后但经由 ADL 查找添加到候选列表。
// #2 在初等模板中得到选择,作为较好匹配。
// 因为 #3 声明后于 #2 ,故它是 #2 的显式特化;
// 从而被选为调用的函数。

输出:

#3


凡在参数为一些 C++ 基础类型时,没有关联到 ADL 的命名空间。从而,这些场景与上述非 ADL 示例等同。


重载决议上的详细规则,见重载决议

[编辑] 函数模板特化

[编辑] 缺陷报告

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

DR 应用于 出版时的行为 正确行为
CWG 1395 C++14 A 来自包时推导失败,且无空包的持平打破 允许推导,添加持平打破

[编辑] 参阅