命名空间
变体
操作

函数模板

来自 cppreference.cn
< cpp‎ | language
 
 
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 < 形参列表 > 函数声明 (1)
template < 形参列表 > requires 约束 函数声明 (2) (自 C++20 起)
带占位符的函数声明 (3) (自 C++20 起)
export template < 形参列表 > 函数声明 (4) (在 C++11 中移除)

[编辑] 解释

形参列表 - 非空的逗号分隔的模板形参列表,其中每个形参要么是非类型形参类型形参模板形参,或是其中任何一种的形参包(自 C++11 起)与任何模板一样,形参可以是受约束的(自 C++20 起)
函数声明 - 函数声明。声明的函数名成为模板名。
约束 - 约束表达式,它限制此函数模板接受的模板实参
函数声明-
带占位符
- 函数声明,其中至少一个形参的类型使用占位符 auto概念 auto:模板形参列表将为每个占位符发明一个形参(见下方的缩写函数模板)

export 是一个可选修饰符,它将模板声明为导出的(当与类模板一起使用时,它声明其所有成员也导出)。实例化导出模板的文件不需要包含其定义:声明就足够了。 export 的实现很少见,并且在细节上彼此不一致。

(直到 C++11)

缩写函数模板

当占位符类型(auto概念 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 起)以及模板头的签名。除了以下情况外,其签名还包含外围命名空间。

如果函数模板是类成员,则其签名包含函数是其成员的类,而不是外围命名空间。其签名还包含尾随 requires 子句(如果有)(自 C++20 起)、ref 限定符(如果有)和(自 C++11 起) cv 限定符(如果有)。

如果函数模板是友元,且约束涉及外围模板形参,则其签名包含外围类而不是外围命名空间。

(自 C++20 起)

[编辑] 函数模板实例化

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

[编辑] 显式实例化

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

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

显式实例化声明(extern template)阻止隐式实例化:否则会导致隐式实例化的代码必须使用程序中其他地方提供的显式实例化定义。

(自 C++11 起)

如果可以从函数形参推导出尾随模板实参,则可以在函数模板特化或成员函数模板特化的显式实例化中将其保留为未指定状态

template<typename T>
void f(T s)
{
    std::cout << s << '\n';
}
 
template void f<double>(double); // instantiates f<double>(double)
template void f<>(char);         // instantiates f<char>(char), template argument deduced
template void f(int);            // instantiates f<int>(int), template argument deduced

函数模板或类模板的成员函数的显式实例化不能使用 inlineconstexpr。如果显式实例化的声明命名了隐式声明的特殊成员函数,则程序是非良构的。

构造函数的显式实例化不能使用模板形参列表(语法 (1)),这也是永远没有必要的,因为它们可以被推导出来(语法 (2))。

预期析构函数的显式实例化必须命名类的选定析构函数。

(自 C++20 起)

显式实例化声明不抑制内联函数、auto声明、引用和类模板特化的隐式实例化。(因此,当作为显式实例化声明主题的内联函数被 ODR 使用时,它会被隐式实例化以进行内联,但其行外副本不会在此翻译单元中生成)

带有默认实参的函数模板的显式实例化定义不是实参的使用,并且不尝试初始化它们

char* p = 0;
 
template<class T>
T g(T x = &p) { return x; }
 
template int g<int>(int); // OK even though &p isn’t an int.

[编辑] 隐式实例化

当代码在需要函数定义存在的上下文中引用函数时,或者如果定义的存​​在会影响程序的语义(自 C++11 起),并且此特定函数尚未显式实例化,则会发生隐式实例化。如果可以从上下文中推导出模板实参列表,则不必提供该列表。

#include <iostream>
 
template<typename T>
void f(T s)
{
    std::cout << s << '\n';
}
 
int main()
{
    f<double>(1); // instantiates and calls f<double>(double)
    f<>('a');     // instantiates and calls f<char>(char)
    f(7);         // instantiates and calls f<int>(int)
    void (*pf)(std::string) = f; // instantiates f<string>(string)
    pf("∇");                     // calls 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 is not potentially constant evaluated
    h<false, int>(0); // error: instantiates f<int> even though B evaluates to false
                      // and list-initialization of int from int cannot be narrowing
}
(自 C++11 起)

注意:完全省略 <> 允许重载解析检查模板和非模板重载。

[编辑] 模板实参推导

为了实例化函数模板,必须知道每个模板实参,但并非每个模板实参都必须指定。如果可能,编译器将从函数实参中推导出缺少的模板实参。当尝试函数调用以及获取函数模板的地址时,会发生这种情况。

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

这种机制使得可以使用模板运算符,因为除了将其重写为函数调用表达式之外,没有语法可以为运算符指定模板实参。

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

模板实参推导发生在函数模板名称查找(可能涉及实参依赖查找)之后和重载解析之前。

有关详细信息,请参见模板实参推导

[编辑] 显式模板实参

函数模板的模板实参可以从以下位置获得:

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

无法为重载运算符转换函数和构造函数显式指定模板实参,因为它们是在不使用函数名称的情况下调用的。

指定的模板实参必须在种类上与模板形参匹配(即,类型对类型、非类型对非类型以及模板对模板)。实参的数量不能多于形参的数量(除非一个形参是形参包,在这种情况下,每个非包形参都必须有一个实参)(自 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);
 
// two different functions with the same type, but 
// within the function, t has different cv qualifications
f<int>(1);       // function type is void(int), t is int
f<const int>(1); // function type is void(int), t is const int
 
// two different functions with the same type and the same x
// (pointers to these two functions are not equal,
//  and function-local statics would have different addresses)
g<int>(1);       // function type is void(int), x is const int
g<const int>(1); // function type is void(int), x is const int
 
// only top-level cv-qualifiers are dropped:
h<const int>(1, NULL); // function type is void(int, const int*) 
                       // z is const int, zp is const int*

[编辑] 函数模板重载

函数模板和非模板函数可以重载。

非模板函数始终与具有相同类型的模板特化不同。不同函数模板的特化始终彼此不同,即使它们具有相同的类型。具有相同返回类型和相同形参列表的两个函数模板是不同的,并且可以通过其显式模板实参列表来区分。

当使用类型或非类型模板形参的表达式出现在函数形参列表或返回类型中时,该表达式仍保留为函数模板签名的一部分,用于重载

template<int I, int J>
A<I+J> f(A<I>, A<J>); // overload #1
 
template<int K, int L>
A<K+L> f(A<K>, A<L>); // same as #1
 
template<int I, int J>
A<I-J> f(A<I>, A<J>); // overload #2

如果包含这些表达式的两个函数定义在ODR下相同,则涉及模板形参的两个表达式称为等价的,即,两个表达式包含相同的标记序列,这些标记的名称通过名称查找解析为相同的实体,除了模板形参可能被不同地命名。两个lambda 表达式永远不等价。(自 C++20 起)

template<int I, int J>
void f(A<I+J>); // template overload #1
 
template<int K, int L>
void f(A<K+L>); // equivalent to #1

在确定两个依赖表达式是否等价时,仅考虑所涉及的依赖名称,而不考虑名称查找的结果。如果同一模板的多个声明在名称查找的结果中有所不同,则使用第一个此类声明

template<class T>
decltype(g(T())) h(); // decltype(g(T())) is a dependent type
 
int g(int);
 
template<class T>
decltype(g(T())) h()
{                  // redeclaration of h() uses earlier lookup
    return g(T()); // although the lookup here does find g(int)
}
 
int i = h<int>(); // template argument substitution fails; g(int)
                  // was not in scope at the first declaration of h()

如果满足以下条件,则认为两个函数模板是等价的

  • 它们在相同的范围内声明
  • 它们具有相同的名称
  • 它们具有等价的模板形参列表,意味着列表的长度相同,并且对于每个对应的形参对,以下所有条件都成立:
  • 两个形参的种类相同(都是类型、都是非类型或都是模板)
  • 它们要么都是形参包,要么都不是
(自 C++11 起)
  • 如果是非类型,则它们的类型是等价的,
  • 如果是模板,则它们的模板形参是等价的,
  • 如果一个是使用概念名称声明的,则两者都是,并且概念名称是等价的。
(自 C++20 起)
  • 它们返回类型和形参列表中涉及模板形参的表达式是等价的
  • 它们模板形参列表之后的 requires 子句(如果存在)中的表达式是等价的
  • 它们函数声明符之后的 requires 子句(如果存在)中的表达式是等价的
(自 C++20 起)

如果两个潜在求值(自 C++20 起)的涉及模板形参的表达式不是等价的,但对于任何给定的模板实参集,两个表达式的求值都会产生相同的值,则它们称为功能上等价的

如果两个函数模板是等价的,但它们的返回类型和形参列表中涉及模板形参的一个或多个表达式是功能上等价的,则认为这两个函数模板是功能上等价的

此外,如果两个函数模板的约束指定不同,但它们接受并由相同的模板实参列表集满足,则它们是功能上等价的但不是等价的

(自 C++20 起)

如果程序包含功能上等价的但不是等价的函数模板的声明,则程序是非良构的;不需要诊断。

// equivalent
template<int I>
void f(A<I>, A<I+10>); // overload #1
template<int I>
void f(A<I>, A<I+10>); // redeclaration of overload #1
 
// not equivalent
template<int I>
void f(A<I>, A<I+10>); // overload #1
template<int I>
void f(A<I>, A<I+11>); // overload #2
 
// functionally-equivalent but not equivalent
// This program is ill-formed, no diagnostic required
template<int I>
void f(A<I>, A<I+10>);      // overload #1
template<int I>
void f(A<I>, A<I+1+2+3+4>); // functionally equivalent

当相同的函数模板特化匹配多个重载函数模板时(这通常是模板实参推导的结果),将执行重载函数模板的部分排序以选择最佳匹配。

具体来说,在以下情况下会发生部分排序:

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);  // first template f
template<class X>
void f(X* a); // second template f
template<>
void f<>(int *a) {} // explicit specialization
 
// template argument deduction comes up with two candidates:
// f<int*>(int*) and f<int>(int*)
// partial ordering selects f<int>(int*) as more specialized

非正式地,“A 比 B 更特化”意味着 “A 接受比 B 更少的类型”。

形式上,为了确定任意两个函数模板中哪一个更特化,偏序过程首先按如下方式转换其中一个模板

  • 对于每个类型、非类型和模板参数,包括参数包,(自 C++11 起) 生成唯一的虚构类型、值或模板,并将其替换到模板的函数类型中
  • 如果被比较的两个函数模板中只有一个是成员函数,并且该函数模板是某个类 A 的非静态成员,则在其参数列表中插入一个新的首参数。给定 cv 作为函数模板的 cv 限定符,以及 ref 作为函数模板的 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; // template argument deduction for int B<A>::operator*(R&) gives R=A 
           //                             for int operator*(T&, R&), T=B<A>, R=A
 
    // For the purpose of partial ordering, the member template B<A>::operator*
    // is transformed into template<class R> int operator*(B<A>&, R&);
 
    // partial ordering between 
    //     int operator*(   T&, R&)  T=B<A>, R=A
    // and int operator*(B<A>&, R&)  R=A 
    // selects int operator*(B<A>&, A&) as more specialized
}

在如上所述转换了两个模板之一后,使用转换后的模板作为实参模板,并使用另一个模板的原始模板类型作为形参模板,执行 模板实参推导。然后重复此过程,使用第二个模板(转换后)作为实参,第一个模板的原始形式作为形参。

用于确定顺序的类型取决于上下文

  • 在函数调用的上下文中,类型是函数调用具有实参的那些函数形参类型(默认函数实参、参数包,(自 C++11 起) 和省略号参数不予考虑 -- 见下例)
  • 在调用用户定义的转换函数的上下文中,使用转换函数模板的返回类型
  • 在其他上下文中,使用函数模板类型

从形参模板的上述列表中推导每种类型。在推导开始之前,形参模板的每个形参 P 和实参模板的对应实参 A 按如下方式调整

  • 如果 PA 在之前都是引用类型,则确定哪个 cv 限定性更强(在所有其他情况下,出于偏序目的,cv 限定符将被忽略)
  • 如果 P 是引用类型,则将其替换为引用的类型
  • 如果 A 是引用类型,则将其替换为引用的类型
  • 如果 P 是 cv 限定的,则 P 将被替换为其 cv 非限定版本
  • 如果 A 是 cv 限定的,则 A 将被替换为其 cv 非限定版本

在这些调整之后,PA 的推导将按照 从类型推导模板实参 的方式进行。

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

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

(自 C++11 起)

如果转换后的模板 1 的实参 A 可以用于推导模板 2 的对应形参 P,但反之则不然,那么对于由此 P/A 对推导的类型,此 AP 更特化。

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

  • 如果 A 是左值引用,而 P 是右值引用,则认为 AP 更特化
  • 如果 AP 的 cv 限定性更强,则认为 AP 更特化

在所有其他情况下,对于由此 P/A 对推导的类型,两个模板都不比另一个更特化。

在考虑了两个方向的每个 PA 之后,如果对于考虑的每种类型,

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

那么模板 1 比模板 2 更特化。如果在切换模板顺序后上述条件仍然成立,则模板 2 比模板 1 更特化。否则,两个模板都不比另一个更特化。

在平局的情况下,如果一个函数模板具有尾随参数包,而另一个没有,则省略参数的那个被认为比具有空参数包的那个更特化。

(自 C++11 起)

如果在考虑了所有重载模板对之后,有一个模板明确地比所有其他模板都更特化,则选择该模板的特化,否则编译失败。

在以下示例中,虚构的实参将被称为 U1、U2

template<class T>
void f(T);        // template #1
template<class T>
void f(T*);       // template #2
template<class T>
void f(const T*); // template #3
 
void m()
{
    const int* p;
    f(p); // overload resolution picks: #1: void f(T ) [T = const int *]
          //                            #2: void f(T*) [T = const int]
          //                            #3: void f(const T *) [T = int]
 
    // partial ordering:
 
    // #1 from transformed #2: void(T) from void(U1*): P=T A=U1*: deduction ok: T=U1*
    // #2 from transformed #1: void(T*) from void(U1): P=T* A=U1: deduction fails
    // #2 is more specialized than #1 with regards to T
 
    // #1 from transformed #3: void(T) from void(const U1*): P=T, A=const U1*: ok
    // #3 from transformed #1: void(const T*) from void(U1): P=const T*, A=U1: fails
    // #3 is more specialized than #1 with regards to T
 
    // #2 from transformed #3: void(T*) from void(const U1*): P=T* A=const U1*: ok
    // #3 from transformed #2: void(const T*) from void(U1*): P=const T* A=U1*: fails
    // #3 is more specialized than #2 with regards to T
 
    // result: #3 is selected
    // in other words, f(const T*) is more specialized than f(T) or 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); // deduction for #1: void f(T, T*) [T = int]
             // deduction for #2: void f(T, int*) [T = int]
 
    // partial ordering:
 
    // #1 from #2: void(T,T*) from void(U1,int*): P1=T, A1=U1: T=U1
    //                                            P2=T*, A2=int*: T=int: fails
 
    // #2 from #1: void(T,int*) from void(U1,U2*): P1=T A1=U1: T=U1
    //                                             P2=int* A2=U2*: fails
 
    // neither is more specialized w.r.t T, the call is ambiguous
}
template<class T>
void g(T);  // template #1
template<class T>
void g(T&); // template #2
 
void m()
{
    float x;
    g(x); // deduction from #1: void g(T ) [T = float]
          // deduction from #2: void g(T&) [T = float]
 
    // partial ordering:
 
    // #1 from #2: void(T) from void(U1&): P=T, A=U1 (after adjustment), ok
 
    // #2 from #1: void(T&) from void(U1): P=T (after adjustment), A=U1: ok
 
    // neither is more specialized w.r.t T, the call is ambiguous
}
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); // deduction from #1: void h(const T &) [T = A<int>]
          // deduction from #2: void h(A<T> &) [T = int]
 
    // partial ordering:
 
    // #1 from #2: void(const T&) from void(A<U1>&): P=T A=A<U1>: ok T=A<U1>
 
    // #2 from #1: void(A<T>&) from void(const U1&): P=A<T> A=const U1: fails
 
    // #2 is more specialized than #1 w.r.t T
 
    const A<int> z2;
    h(z2); // deduction from #1: void h(const T&) [T = A<int>]
           // deduction from #2: void h(A<T>&) [T = int], but substitution fails
 
    // only one overload to choose from, partial ordering not tried, #1 is called
}

由于调用上下文仅考虑具有显式调用实参的形参,因此那些函数参数包、(自 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); // calls #2 (T* is more specialized than T)
}
template<class T>
void g(T);       // #1
template<class T>
void g(T*, ...); // #2
 
void m(int* ip)
{
    g(ip); // calls #2 (T* is more specialized than 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); // calls #2
    f<int>(42);                  // error: ambiguous
}
template<class T>
void g(T, T = T()); // #1
template<class T, class... U>
void g(T, U...);    // #2
 
void h()
{
    g(42); // error: ambiguous
}
template<class T, class... U>
void f(T, U...); // #1
template<class T>
void f(T);       // #2
 
void h(int i)
{
    f(&i); // calls #2 due to the tie-breaker between parameter pack and no parameter
           // (note: was ambiguous between DR692 and 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: calls #1 (T* is more specialized than T)
}
template<class... T>
int f(T*...);    // #1
template<class T>
int f(const T&); // #2
 
f((int*)0); // OK: selects #2; non-variadic template is more specialized than
            // variadic template (was ambiguous before DR1395 because deduction
            // failed in both directions)
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();        // calls #1
f(1, 2, 3); // calls #2
f(1, 2);    // calls #3; non-variadic template #3 is more
            // specialized than the variadic templates #1 and #2

在偏序过程中的模板实参推导期间,如果实参未在为偏序考虑的任何类型中使用,则模板形参不需要与实参匹配

template<class T>
T f(int); // #1
template<class T, class U>
T f(U);   // #2
 
void g()
{
    f<int>(1); // specialization of #1 is explicit: T f(int) [T = int]
               // specialization of #2 is deduced:  T f(U) [T = int, U = int]
 
    // partial ordering (only considering the argument type):
 
    // #1 from #2: T(int) from U1(U2): fails
    // #2 from #1: T(U) from U1(int): ok: U=int, T unused
 
    // calls #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 起)

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

template<class T>
void f(T);      // #1: template overload
template<class T>
void f(T*);     // #2: template overload
 
void f(double); // #3: non-template overload
template<>
void f(int);    // #4: specialization of #1
 
f('a');        // calls #1
f(new int(1)); // calls #2
f(1.0);        // calls #3
f(1);          // calls #4

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

请注意,只有非模板重载和主模板重载参与重载决议。特化不是重载,因此不被考虑。只有在重载决议选择最佳匹配的主函数模板后,才会检查其特化,以查看是否有更好的匹配。

template<class T>
void f(T);    // #1: overload for all types
template<>
void f(int*); // #2: specialization of #1 for pointers to int
template<class T>
void f(T*);   // #3: overload for all pointer types
 
f(new int(1)); // calls #3, even though specialization of #1 would be a perfect match

在对翻译单元的头文件进行排序时,记住此规则非常重要。有关函数重载和函数特化之间相互作用的更多示例,请展开下方

示例

首先考虑一些没有采用实参依赖查找的场景。为此,我们使用调用 (f)(t)。正如 ADL 中所述,将函数名括在括号中会抑制实参依赖查找。

  • g() 中的引用点 (POR) 之前声明的 f() 的多个重载。
#include <iostream>
 
struct A {};
 
template<class T>
void f(T)  { std::cout << "#1\n"; } // overload #1 before f() POR
template<class T>
void f(T*) { std::cout << "#2\n"; } // overload #2 before f() POR
 
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
 
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
 
// Both #1 and #2 are added to the candidate list;
// #2 is selected because it is a better match.

输出

#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); // 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


  • 更好的匹配显式模板特化在 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); // POR of g() and f()
}
 
// #1 is added to the candidate list; #3 is a better match defined after POR. The
// candidate list consists of #1 which is eventually selected. After that, the explicit 
// specialization #3 of #1 declared after POI is selected because it is a better match. 
// This behavior is governed by 14.7.3/6 [temp.expl.spec] and has nothing to do with 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); // POR of g() and f()
}
 
// #1 is the only member of the candidate list and it is eventually selected. 
// After that, the explicit specialization #3 is skipped because it actually 
// specializes #2 declared after POR.

输出

#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); // 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 being the better match.

输出

#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); // 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


  • 更好的匹配模板重载在 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); // 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 after #2, it is an explicit specialization of #2;
// therefore, selected as the function to call.

输出

#3


每当实参是一些 C++ 基本类型时,就没有与 ADL 关联的命名空间。因此,这些场景与上面的非 ADL 示例相同。

有关重载决议的详细规则,请参阅 重载决议

[编辑] 函数模板特化

[编辑] 关键字

template, extern (自 C++11 起)

[编辑] 缺陷报告

以下行为变更缺陷报告已追溯应用于先前发布的 C++ 标准。

DR 应用于 已发布行为 正确行为
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 为其添加的新首参数的类型
非静态成员函数模板是
与该模板的 ref 限定符无关
类型是右值
引用类型,如果
ref 限定符为 &&
CWG 1446 C++11 为非静态成员添加的新首参数的类型
没有 ref 限定符的函数模板是左值引用
类型,即使该成员函数模板与
首参数具有右值引用类型的函数模板进行比较
类型是
右值引用
在这种情况下类型
CWG 2373 C++98 新的首参数已添加到参数列表中
静态成员函数模板在偏序中
未添加

[编辑] 另请参阅