Namespaces
Variants

Function template

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Class template
Function template
Miscellaneous

函数模板定义了一族函数。

目录

语法

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 移除)

说明

parameter-list - 一个非空的逗号分隔的 模板参数 列表,其中每个参数可以是 常量参数 类型参数 模板参数 ,或是这些参数类型的 参数包 (C++11 起) 与任何模板一样,参数可以是 受约束的 (C++20 起)
function-declaration - 一个 函数声明 。声明的函数名将成为模板名。
constraint - 一个 约束表达式 ,用于限制此函数模板接受的模板参数
function-declaration-
with-placeholders
- 一个 函数声明 ,其中至少一个参数的类型使用了占位符 auto Concept auto :模板参数列表将为每个占位符生成一个合成参数(参见下文简写函数模板)

export 曾是一个可选修饰符,用于将模板声明为 已导出 (当用于类模板时,会同时声明其所有成员为已导出)。实例化已导出模板的文件无需包含其定义:仅声明即可。实际实现 export 功能的编译器较为罕见,且在具体细节上存在分歧。

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


(since C++20)

函数模板签名

每个函数模板都有一个签名。

模板头的签名是 模板形参列表 ,不包括模板形参名称和 默认实参 ,以及 requires 子句(如果存在) (C++20 起)

函数模板的签名包含名称、形参类型列表、返回类型 、尾部requires子句(如果存在) (C++20起) ,以及 模板头 的签名。除以下情况外,其签名还包含所在的命名空间。

若函数模板是类成员,其签名包含该函数所属的类而非外围命名空间。其签名还包含 尾随requires子句(若存在) (C++20 起) 、引用限定符(若存在)以及 (C++11 起) cv -限定符(若存在)。

若函数模板是涉及外围模板参数的 友元 且带有约束,其签名将包含外围类而非外围命名空间。

(since C++20)

函数模板实例化

函数模板本身并非类型,亦非函数。仅包含模板定义的源文件不会生成任何代码。为使代码实际生成,模板必须被实例化:必须确定模板参数,以便编译器能够生成具体的函数(或从类模板生成类)。

显式实例化

template 返回类型 名称 < 参数列表 > ( 形参列表 ) ; (1)
template 返回类型 名称 ( 形参列表 ) ; (2)
extern template 返回类型 名称 < 参数列表 > ( 形参列表 ) ; (3) (C++11 起)
extern template 返回类型 名称 ( 形参列表 ) ; (4) (C++11 起)
1) 显式实例化定义(当每个非默认模板参数均被显式指定时,无需进行 模板实参推导
2) 对所有参数进行模板实参推导的显式实例化定义
3) 显式实例化声明(当每个非默认模板参数都被显式指定时,不进行模板参数推导)
4) 显式实例化声明(所有参数均采用模板实参推导)

显式实例化定义强制实例化其所引用的函数或成员函数。它可以出现在模板定义之后的程序任意位置,并且对于给定的实参列表,只允许在程序中出现一次,不要求诊断信息。

显式实例化声明(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)

显式实例化声明不会抑制 inline 函数、 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); // 正确:B ? f<T>() : 0 不进行潜在常量求值
    h<false, int>(0); // 错误:即使B求值为false仍实例化f<int>
                      // 且从int到int的列表初始化不能进行窄化转换
}
(since C++11)

注意:完全省略 <> 允许 重载决议 同时检查模板和非模板重载。

模板实参推导

为了实例化函数模板,必须知晓所有模板参数,但并非所有模板参数都需要显式指定。在可能的情况下,编译器会从函数参数中推导缺失的模板参数。这种情况发生在尝试进行函数调用以及获取函数模板地址时。

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

模板实参推导发生在函数模板 名称查找 (可能涉及 实参依赖查找 )之后,并在 重载决议 之前进行。

参见 模板实参推导 了解详情。

显式模板参数

函数模板的模板参数可从

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

无法显式指定模板参数给 重载运算符 转换函数 以及构造函数,因为这些函数在被调用时并未使用函数名。

指定的模板实参必须在种类上与模板形参匹配(即类型对应类型,常量对应常量,模板对应模板)。实参数量不能超过形参数量 (除非某个形参是形参包,此时每个非包形参都必须有对应实参) (C++11 起)

指定的常量实参必须与对应常量模板形参的类型相匹配,或者能够 转换为这些形参类型

不参与模板实参推导的函数形参(例如当对应的模板实参被显式指定时),会经历到对应函数形参类型的隐式转换(如同常规的 重载决议 过程)。

被显式指定的模板参数包可通过模板实参推导进行扩展(当存在额外实参时):

template<class... Types>
void f(Types... values);
void g()
{
    f<int*, float*>(0, 0, 0); // Types = {int*, float*, int}
}
(since 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 规则下是相同的,即这两个表达式包含相同的token序列,且这些token的名称通过名称查找解析为相同实体,但模板参数可以具有不同命名。 两个 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() 的首次声明时不在作用域内

两个函数模板在以下情况下被视为 等价

  • 它们声明在同一作用域内
  • 它们具有相同的名称
  • 它们具有 等价 的模板参数列表,这意味着列表长度相同,且对于每个对应的参数对,以下所有条件均成立:
  • 两个参数属于同一种类(均为类型、均为常量或均为模板)
  • 它们要么都是参数包,要么都不是
(since C++11)
  • 若为常量,则它们的类型是等价的,
  • 若为模板,则它们的模板参数是等价的,
  • 如果其中一个使用概念名声明,则两者都使用概念名声明,且概念名等价。
(since C++20)
  • 返回类型和参数列表中涉及模板参数的表达式是 等价
  • 若存在模板形参列表后的 requires 子句中的表达式,则它们等价
  • 若存在函数声明符后的 requires 子句中的表达式,则它们等价
(since 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>); // 功能等价

当同一函数模板特化匹配多个重载函数模板时(这通常由 模板实参推导 导致),将执行 重载函数模板的偏序规则 以选择最佳匹配。

具体而言,在以下情况下会发生偏序化:

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) 当选择作为函数模板特化的 placement operator delete 来匹配 placement operator new 时:
4) 友元函数声明 显式实例化 显式特化 引用函数模板特化时:
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 起) ,则新参数类型为 cv A& ,除非 ref && ,或 ref 不存在且另一模板的首参数具有右值引用类型,此时类型为 cv A&& (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 的过程遵循 从类型推导模板参数 的规则。

P 是函数形参包,则将实参模板的每个剩余形参类型 A 与函数形参包的声明符标识符类型 P 进行比较。每次比较都会为函数形参包展开的模板形参包中的后续位置推导模板实参。

A 由函数形参包转换而来,则将其与形参模板的每个剩余形参类型进行比较。

(C++11 起)

如果变换后模板1的参数 A 可用于推导模板2的对应参数 P ,但反之不成立,则就通过该 P/A 对推导的类型而言,此 A P 更特化。

如果两个方向的推导都成功,且原始的 P A 是引用类型,则会进行额外测试:

  • 如果 A 是左值引用且 P 是右值引用,则认为 A P 更特化
  • 如果 A 的 cv 限定符比 P 更多,则认为 A P 更特化

在所有其他情况下,就这个 P/A 对推导出的类型而言,两个模板都不比另一个更特化。

在双向考虑每个 P A 之后,如果对于每个被考虑的类型,

  • 对于所有类型,模板1至少与模板2具有同等特化程度
  • 对于某些类型,模板1比模板2更特化
  • 对于任何类型,模板2都不比模板1更特化,或者对于任何类型都不具有同等特化程度

那么模板1比模板2更特化。如果调换模板顺序后上述条件仍然成立,则模板2比模板1更特化。否则,两个模板之间不存在更特化关系。

当出现平局时,若某个函数模板具有尾部形参包而另一个没有,则省略形参包的模板被视为比具有空形参包的模板更特化。

(since 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(U1,int*) 推导 void(T,T*):P1=T, A1=U1: T=U1
    //                                            P2=T*, A2=int*: T=int:失败
    // #2 与 #1 比较:从 void(U1,U2*) 推导 void(T,int*):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]
    // 偏序规则(仅考虑参数类型):
    // 从 #2 推导 #1:T(int) 来自 U1(U2):推导失败
    // 从 #1 推导 #2: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<>());            // 调用 #1
g(Tuple<int, float>());  // 调用 #2
g(Tuple<int, float&>()); // 调用 #3
g(Tuple<int>());         // 调用 #3
(since C++11)

要编译对函数模板的调用,编译器必须在非模板重载、模板重载以及模板重载的特化之间做出决策。

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 中所述,将函数名用括号包裹会抑制实参依赖查找。

  • g ( ) 中的引用点(POR)之前声明了多个 f ( ) 的重载。
#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


  • 更好的匹配模板重载在 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() 的 POR
}
// 只有 #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() 的 POR
}
// #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 << <span class

有关重载解析的详细规则,请参阅 重载解析

函数模板特化

关键词

template , extern (自 C++11 起)

缺陷报告

下列行为变更缺陷报告被追溯应用于先前发布的 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 偏序处理中为静态成员函数模板
的参数列表添加了新增首参数
不再添加

参见