命名空间
变体
操作

函数模板

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
 
 
 
 

函数模板定义了一系列函数。

目录

[编辑] 语法

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-clause(如果有)(自 C++20 起)

函数模板的签名包含名称、参数类型列表、返回类型、尾随 requires-clause(如果有)(自 C++20 起) 以及 模板头 的签名。 除以下情况外,其签名还包含封闭命名空间。

如果函数模板是类成员,则其签名包含函数所属的类,而不是封闭命名空间。 其签名还包含 尾随 requires-clause(如果有)(自 C++20 起)、引用限定符(如果有)以及(自 C++11 起) cv 限定符(如果有)。

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

(C++20 起)

[编辑] 函数模板实例化

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

[编辑] 显式实例化

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

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

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

(自 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 起)
  • 如果是非类型,则它们的类型等效
  • 如果是模板,则它们的模板参数等效
  • 如果一个是用 concept-name 声明的,则它们都是,并且 concept-name 等效。
(C++20 起)
  • 它们在返回类型和参数列表中涉及模板参数的表达式是*等效的*
  • 如果存在,则它们在模板参数列表之后的 requires-clauses 中的表达式是等效的
  • 如果存在,则它们在函数声明符之后的 requires-clauses 中的表达式是等效的
(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) 选择作为函数模板特化的 放置 delete 运算符 以匹配放置 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 是函数模板的引用限定符,(自 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 非限定版本

在进行这些调整之后,将按照 从类型推导模板参数 的规则从 A 中推导出 P

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

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

(自 C++11 起)

如果可以使用转换后的模板 1 的参数 A 来推导出模板 2 的对应参数 P,反之则不行,则认为该 A 在该 P/A 对推导出的类型方面比 P 更特殊。

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

  • 如果 A 是左值引用,而 P 是右值引用,则认为 AP 更特殊
  • 如果 A 的 cv 限定符比 P 多,则认为 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

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

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

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

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

示例

首先考虑一些不使用依赖于参数的查找(ADL)的情况。为此,我们使用调用 (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 为非静态成员函数模板添加的
新第一个参数的类型
与该模板的引用限定符无关
该类型是右值
引用类型,如果
引用限定符是 &&
CWG 1446 C++11 为没有引用限定符的非静态成员
函数模板添加的新第一个参数的类型是左值引用
类型,即使该成员函数模板与
第一个参数具有右值引用类型的函数模板进行比较
在这种情况下,该类型是
右值引用
类型
CWG 2373 C++98 在偏序中,为静态成员函数模板的参数列表
添加了新的第一个参数
未添加

[编辑] 另请参阅