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++ 关键词
部分模板特化
允许为给定类别的模板实参定制类模板或变量模板 (C++14 起)。
语法
template < 形参列表 > 类关键词 类头名 < 实参列表 > 声明
|
|||||||||
template < 形参列表 > 声明说明符序列 声明符 < 实参列表 > 初始化器(可选)
|
(2) | (C++14 起) | |||||||
其中 类头名 标识先前声明的类模板,而 声明符 标识先前声明的变量模板 (C++14 起)。此声明必须与它所特化的主模板定义处于相同的命名空间中,或对于成员模板,处于相同的类作用域中。
例如,
template<class T1, class T2, int I> class A {}; // 主模板 template<class T, int I> class A<T, T*, I> {}; // #1:部分特化,其中 T2 是指向 T1 的指针 template<class T, class T2, int I> class A<T*, T2, I> {}; // #2:部分特化,其中 T1 是指针 template<class T> class A<int, T*, 5> {}; // #3:部分特化,其中 T1 是 int,I 是 5,而 T2 是指针 template<class X, class T, int I> class A<X, T*, I> {}; // #4:部分特化,其中 T2 是指针
标准库中的部分特化的例子包含 std::unique_ptr,它对数组类型有部分特化。
实参列表
下列限制应用于部分模板特化的 实参列表:
template<class T1, class T2, int I> class B {}; // 主模板 template<class X, class Y, int N> class B<X,Y,N> {}; // 错误
此外,特化必须比主模板更特殊 template<int N, typename T1, typename... Ts> struct B; template<typename... Ts> struct B<0, Ts...> { }; // 错误:并不更特殊 |
(C++14 起) |
template <int I, int J> struct A {}; template <int I> struct A<I+5, I*2> {}; // 错误:I 不可推导 template <int I, int J, int K> struct B {}; template <int I> struct B<I, I*2, 2> {}; // OK:首个形参可推导
template <class T, T t> struct C {}; // 主模板 template <class T> struct C<T, 1>; // 错误:1 的类型是 T, // 它依赖于形参 T template< int X, int (*array_ptr)[X] > class B {}; // 主模板 int array[5]; template< int X > class B<X,&array> { }; // 错误:实参类型 &array 是 int(*)[X],依赖于形参 X
名字查找
名字查找不会找到部分模板特化。仅当主模板被名字查找所找到时,才考虑其部分特化。特别是令主模板可见的 using 声明,亦令部分特化可见:
namespace N { template<class T1, class T2> class Z { }; // 部分特化 } using N::Z; // 指代主模板 namespace N { template<class T> class Z<T, T*> { }; // 部分特化 } Z<int,int*> z; // 名字查找找到 N::Z(主模板), // 然后使用带 T = int 的部分特化
部分排序
当实例化类模板或变量模板 (C++14 起),且有部分特化可用时,编译器必须决定是继续使用主模板还是使用其部分特化之一。
// 给定上面所定义的模板 A A<int, int, 1> a1; // 无匹配的特化,使用主模板 A<int, int*, 1> a2; // 用部分特化 #1,(T=int, I=1) A<int, char*, 5> a3; // 用部分特化 #3,(T=char) A<int, char*, 1> a4; // 用部分特化 #4,(X=int, T=char, I=1) A<int*, int*, 2> a5; // 错误:匹配 #2 (T=int, T2=int*, I=2) // 匹配 #4 (X=int*, T=int, I=2) // 无一者比另一者更特殊
非正式而言,“A 比 B 更特殊”的意思是“A 接受 B 所接受类型的子集”。
正式而言,为在部分特化之间建立“更特殊”关系,首先将其中每一个都转换成下列的虚设函数模板:
- 第一个函数模板拥有与第一个部分特化相同的模板形参,且只有一个函数形参,其类型是带所有来自第一个部分特化的模板实参的类模板特化
- 第二个函数模板拥有与第二个部分特化相同的模板形参,且只有一个函数形参,其类型是带所有来自第一个部分特化的模板实参的类模板特化。
然后,如同为函数模板重载所做的一样,对函数模板排行。
template<int I, int J, class T> struct X { }; // 主模板 template<int I, int J> struct X<I, J, int> { static const int s = 1; }; // 部分特化 #1 // #1 的虚设函数模板是 // template<int I, int J> void f(X<I, J, int>); #A template<int I> struct X<I, I, int> { static const int s = 2; }; // 部分特化 #2 // #2 的虚设函数模板是 // template<int I> void f(X<I, I, int>); #B int main() { X<2, 2, int> x; // #1 和 #2 都要匹配 // 函数模板的部分排序: // #A 从 #B:void(X<I,J,int>) 从 void(X<U1, U1, int>):推导 ok // #B 从 #A:void(X<I,I,int>) 从 void(X<U1, U2, int>):推导失败 // #B 更特殊 // #2 为所实例化的特化 std::cout << x.s << '\n'; // 打印 2 }
部分特化的成员
部分特化的成员的模板形参列表和模板实参列表,必须与部分特化的形参列表和实参列表相匹配。
正如主模板的成员一样,仅当它们在程序中使用时,才需要定义。
部分特化的成员与主模板的成员无关。
部分特化的成员的显式(全)特化以与主模板的显式特化相同的方式声明。
template<class T, int I> // 主模板 struct A { void f(); // 成员声明 }; template<class T, int I> void A<T,I>::f() { } // 主模板成员定义 // 部分特化 template<class T> struct A<T,2> { void f(); void g(); void h(); }; // 部分特化的成员 template<class T> void A<T,2>::g() { } // 部分特化的成员的显式(全)特化 template<> void A<char,2>::h() {} int main() { A<char,0> a0; A<char,2> a2; a0.f(); // OK,用主模板的成员定义 a2.g(); // OK,用部分特化的成员定义 a2.h(); // OK,用部分特化的成员的全特化定义 a2.f(); // 错误:部分特化 A<T,2> 中无 f() 的定义(不使用主模板) }
若主模板是另一个类模板的成员,则其部分特化也是外围模板类的成员。若外围模板类被实例化,则每个成员部分特化的声明也被实例化(模板的所有其他成员的声明(但非其定义)也是以相同方式实例化的)
若针对外围类模板的给定(隐式)特化进行主成员模板的显式(全)特化,则对外围类模板的这个特化忽略该成员模板的部分特化。
若针对外围类模板的给定(隐式)特化进行成员模板的某个部分特化的显式特化,则对外围类模板的这个特化仍然考虑主成员模板及其其他部分特化。
template<class T> struct A { // 外围类模板 template<class T2> struct B {}; // 主成员模板 template<class T2> struct B<T2*> {}; // 成员模板的部分特化 }; template<> template<class T2> struct A<short>::B {}; // 主成员模板的全特化(忽略部分特化) A<char>::B<int*> abcip; // 使用部分特化 T2=int A<short>::B<int*> absip; // 使用主模板的全特化(忽略部分特化) A<char>::B<int> abci; // 使用主模板
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1315 | C++14 | 不能在除标识表达式之外的非类型实参表达式中使用模板形参 | 只要能推导,表达式就 OK |