C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
函数模板
函数模板定义一族函数。
语法
template < 形参列表 > 函数声明
|
(1) | ||||||||
template < 形参列表 > requires 制约 函数声明
|
(2) | (C++20 起) | |||||||
带占位符函数声明 | (3) | (C++20 起) | |||||||
export template < 形参列表 > 函数声明
|
(4) | (C++11 前) | |||||||
解释
形参列表 | - | 非空的模板形参的逗号分隔列表,每项是非类型形参、类型形参、模板形参或这些的形参包之一。与任何模板一样,形参可以有制约。 (C++20 起) |
函数声明 | - | 函数声明。所声明的函数名成为模板名。 |
制约(C++20) | - | 制约表达式,它限制此函数模板接受的模板形参。 |
带占位符函数声明(C++20) | - | 函数声明,其中至少一个形参的类型使用了占位符 auto 或 Concept auto:模板形参列表将对每个占位符拥有一个虚设形参。(见下文“简写函数模板”)
|
|
(C++11 前) |
简写函数模板当函数声明或函数模板声明的形参列表中出现占位符类型( void f1(auto); // 与 template<class T> void f(T) 相同 void f2(C1 auto); // 若 C1 是概念,则与 template<C1 T> void f2(T) 相同 void f3(C2 auto...); // 若 C2 是概念,则与 template<C2... Ts> void f3(Ts...) 相同 void f4(const C3 auto*, C4 auto&); // 与 template<C3 T, C4 U> void f4(const T*, U&); 相同 template <class T, C U> void g(T x, U y, C auto z); // 与 template<class T, C U, C W> void g(T x, U y, W z); 相同 简写函数模板可以和所有函数模板一样进行特化。 template<> void f4<int>(const int*, const double&); // f4<int, const double> 的特化
|
(C++20 起) |
函数模板实例化
函数模板自身并不是类型、函数或任何实体。从仅包含模板定义的源文件不生成任何代码。为使得代码出现必须实例化模板:必须确定各模板实参,以令编译器能生成一个实际的函数(或从类模板生成类)。
显式实例化
template 返回类型 名字 < 实参列表 > ( 形参列表 ) ;
|
(1) | ||||||||
template 返回类型 名字 ( 形参列表 ) ;
|
(2) | ||||||||
extern template 返回类型 名字 < 实参列表 > ( 形参列表 ) ;
|
(3) | (C++11 起) | |||||||
extern template 返回类型 名字 ( 形参列表 ) ;
|
(4) | (C++11 起) | |||||||
显式实例化定义强制实例化其所指代的函数或成员函数。它可以出现在程序中模板定义后的任何位置,而对于给定的实参列表,只允许它在整个程序中出现一次。
显式实例化声明(extern 模板)阻止隐式实例化:本来会导致隐式实例化的代码,必须使用已在程序的别处所提供的显式实例化。 |
(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) )。
显式实例化声明并不抑制 inline 函数,auto 声明,引用,以及类模板特化的隐式实例化。(从而,当作为显式实例化声明目标的 inline 函数被 ODR 式使用时,函数将为内联而隐式实例化,但此翻译单元中不生成其非内联副本)
带有默认实参的函数模板的显式实例化定义,不是对该实参的使用,且不会试图实例化之:
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) }
此机制使得使用模板运算符可行,因为除了将其重写为函数调用表达式之外,不存在为运算符指定模板实参的语法。
模板实参推导在函数模板的名字查找(可能涉及实参依赖查找)之后,在重载决议之间进行。
细节见模板实参推导。
显式模板实参
函数模板的模板实参可从以下途径获得
- 模板实参推导
- 默认模板实参
- 显式指定,可以在下列语境中进行:
- 于函数调用表达式中
- 当取函数地址时
- 当初始化到函数的引用时
- 当构成成员函数指针时
- 于显式特化中
- 于显式实例化中
- 于友元声明中
不存在为重载的运算符、转换函数及构造函数显式指定模板实参的方法,因为它们的调用中并未使用函数名。
所指定的各模板实参必须与各模板形参在种类上相匹配(即类型对类型,非类型对非类型,模板对模板)。不能有多于形参数量的实参(除非形参之一是形参包,这种情况下对每个非包形参必须有一个实参)。
所指定的非类型实参必须要么与其对应的非类型模板形参的类型相匹配,要么可转换成它们。
不参与模板实参推导的函数实参(例如若对应的模板实参已被显式指定),参与到其对应函数形参类型的隐式转换(如在通常重载决议中一样)。
当有额外的实参时,模板实参推导可以对显式指定的模板形参包进行扩充:
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>(); // 模板实参替换失败;在 h() 的首个声明处 g(int) 不在作用域中 |
(C++14 起) |
当足下列条件时,认为两个函数模板等价
- 它们声明于同一作用域
- 它们具有相同的名字
- 它们拥有等价的模板形参列表,含义是列表长度相同,且对于每对对应的形参,下列内容全为真:
- 二个形参的种类相同(均为类型、均为非类型或均为模板),
- 它们都是形参包或都不是,
- 若是非类型,则其类型等价,
- 若是模板,则其模板形参等价,
|
(C++20 起) |
- 其返回类型和形参列表中,各个涉及模板实参的表达式均等价
|
(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>); // 功能等价
当同一个函数模板特化与多于一个重载的函数模板相匹配时(这通常由模板实参推导所导致),进行重载函数模板的部分排序以选择最佳匹配。
具体而言,在以下情形中发生部分排序:
template<class X> void f(X a); template<class X> void f(X* a); void (*p)(int*) = &f;
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
进行调整:
- 若
P
与A
此前均为引用类型,则确定哪个更为 cv 限定(其他所有情况下,就部分排序而言都忽略 cv 限定) - 若
P
是引用类型,则以其所引用的类型替换它 - 若
A
是引用类型,则以其所引用的类型替换它 - 若
P
有 cv 限定,则P
被替换为自身的无 cv 限定版本 - 若
A
有 cv 限定,则A
被替换为自身的无 cv 限定版本
在这些调整后,遵循从类型进行模板实参推导规则,从 A
推导 P
。
若 P 是函数形参包,则实参模板的每个剩余形参类型的类型 A,都与该函数参数包的声明符标识的类型 P 进行比较。每次比较都为该函数参数包所展开的模板参数包中的后继位置的进行模板实参的推导。
若 A 从函数参数包变换而来,则推导失败。 (C++14 前)将它与形参模板的每个剩余形参类型进行比较。 (C++14 起)
若变换后的模板 1 的实参 A
可用来推导模板 2 的对应形参 P
,但反之不可,则对于从这一对 P/A 所推导的类型而言,这个 A
比 P
更特殊。
若双向推导均成功,且原 P
与 A
是引用类型,则做附加的测试:
- 若
A
是左值引用而P
是右值引用,则认为 A 比 P 更特殊 - 若
A
比P
更有 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
本节未完成 原因:14.8.3[temp.over] |
为编译对函数模板的调用,编译器必须在非模板重载、模板重载和模板重载的特化间作出决定。
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 中的描述,将函数名包在括号中可抑制实参依赖查找。
运行此代码 #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
运行此代码 #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
运行此代码 #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
运行此代码 #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
运行此代码 #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
运行此代码 #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
运行此代码 #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
|
有关重载决议的详细规则,见重载决议。
函数模板特化
本节未完成 原因:14.8[temp.fct.spec] (note that 14.8.1[temp.arg.explicit] 已在全特化专题:要么让函数特化到这里:失去部分特化、与函数重载的交互,或仅引用那边 |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1395 | C++14 | 从形参包推导 A 时失败,且对于空形参包没有决胜规则 | 允许推导,添加决胜规则 |