命名空间
变体
操作

函数模板

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

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

目录

[编辑] 语法

template < 参数列表 > 函数声明 (1)
template < 参数列表 > requires 约束 函数声明 (2) (C++20 起)
带占位符的函数声明 (3) (C++20 起)
export template < 参数列表 > 函数声明 (4) (C++11 中已移除)

[编辑] 解释

参数列表 - 一个非空的逗号分隔的模板参数列表,其中每个参数要么是非类型参数类型参数模板参数,或上述任意一种的参数包(C++11 起)如同任何模板一样,参数可以被约束(C++20 起)
函数声明 - 一个函数声明。声明的函数名成为模板名。
约束 - 一个约束表达式,用于限制此函数模板接受的模板参数
函数声明-
带占位符
- 一个函数声明,其中至少一个参数的类型使用占位符autoConcept auto:模板参数列表将为每个占位符附加一个发明的参数(参阅下面的缩写函数模板)

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

(C++11 前)

缩写函数模板

当占位符类型(autoConcept 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 起)、引用限定符(如果有),以及(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) 当选择作为函数模板特化的定位 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 作为函数模板的 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,但反之不行,则此 A 在此 P/A 对推导的类型方面比 P 更特化。

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

  • 如果 A 是左值引用而 P 是右值引用,则 A 被认为比 P 更特化。
  • 如果 AP 具有更多的 cv 限定符,则 A 被认为比 P 更特化。

在所有其他情况下,对于此 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

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

示例

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

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

缺陷报告 应用于 发布时的行为 正确的行为
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-qualifier 无关
如果 ref-qualifier 为 &&
则类型是右值引用类型。
ref-限定符为 &&
CWG 1446 C++11 为无 ref 限定符的非静态成员函数模板添加的新第一个参数的类型是左值引用类型,即使该成员函数模板与第一个参数具有右值引用类型的函数模板进行比较。
即使该成员函数模板与第一个参数具有右值引用类型的函数模板进行比较
类型,即使该成员函数模板与
函数模板(其第一个参数具有右值引用类型)进行比较
在这种情况下,该类型是
右值引用
类型
CWG 2373 C++98 在部分排序中,新的第一个参数被添加到静态成员函数模板的参数列表中
静态成员函数模板的参数列表中
未添加

[编辑] 参阅