|
实验平台:Win7,VS2013 Community,GCC 4.8.3(在线版)
所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得异常灵活,能实现很多高级动态语言才有的特性(语法上可能比较丑陋,一些历史原因见下文)。
普通用户对 C++ 模板的使用可能不是很频繁,大致限于泛型编程,但一些系统级的代码,尤其是对通用性、性能要求极高的基础库(如 STL、Boost)几乎不可避免的都大量地使用 C++ 模板,
一个稍有规模的大量使用模板的程序,不可避免的要涉及元编程(如类型计算)。本文就是要剖析 C++ 模板元编程的机制。
下面所给的所有代码,想做实验又懒得打开编译工具?一个在线运行 C++ 代码的网站(GCC 4.8)很好~
1. C++模板的语法
函数模板(function template)和类模板(class template)的简单示例如下:
- #include <iostream>
- // 函数模板
- template<typename T>
- bool equivalent(const T& a, const T& b) {
- return !(a < b) && !(b < a);
- }
- // 类模板
- template<typename T=int> // 默认参数
- class bignumber{
- T _v;
- public:
- bignumber(T a) : _v(a) { }
- inline bool operator<(const bignumber& b) const; // 等价于 (const bignumber<T>& b)
- };
- // 在类模板外实现成员函数
- template<typename T>
- bool bignumber<T>::operator<(const bignumber& b) const{
- return _v < b._v;
- }
- int main()
- {
- bignumber<> a(1), b(1); // 使用默认参数,"<>"不能省略
- std::cout << equivalent(a, b) << '\n'; // 函数模板参数自动推导
- std::cout << equivalent<double>(1, 2) << '\n';
- std::cin.get(); return 0;
- }
复制代码 程序输出如下:
关于模板(函数模板、类模板)的模板参数(详见文献[1]第3章):
- 类型参数(type template parameter),用 typename 或 class 标记;
- 非类型参数(non-type template parameter)可以是:整数及枚举类型、对象或函数的指针、对象或函数的引用、对象的成员指针,非类型参数是模板实例的常量;
- 模板型参数(template template parameter),如“template<typename T, template<typename> class A> someclass {};”;
- 模板参数可以有默认值(函数模板参数默认是从 C++11 开始支持);
- 函数模板的和函数参数类型有关的模板参数可以自动推导,类模板参数不存在推导机制;
- C++11 引入变长模板参数,请见下文。
模板特例化(template specialization,又称特例、特化)的简单示例如下:
- // 实现一个向量类
- template<typename T, int N>
- class Vec{
- T _v[N];
- // ... // 模板通例(primary template),具体实现
- };
- template<>
- class Vec<float, 4>{
- float _v[4];
- // ... // 对 Vec<float, 4> 进行专门实现,如利用向量指令进行加速
- };
- template<int N>
- class Vec<bool, N>{
- char _v[(N+sizeof(char)-1)/sizeof(char)];
- // ... // 对 Vec<bool, N> 进行专门实现,如用一个比特位表示一个bool
- };
复制代码 所谓模板特例化即对于通例中的某种或某些情况做单独专门实现,最简单的情况是对每个模板参数指定一个具体值,这成为完全特例化(full specialization),另外,可以限制模板参数在一个范围取值或满足一定关系等,这称为部分特例化(partial specialization),用数学上集合的概念,通例模板参数所有可取的值组合构成全集U,完全特例化对U中某个元素进行专门定义,部分特例化对U的某个真子集进行专门定义。
更多模板特例化的例子如下(参考了文献[1]第44页):
- template<typename T, int i> class cp00; // 用于模板型模板参数
- // 通例
- template<typename T1, typename T2, int i, template<typename, int> class CP>
- class TMP;
- // 完全特例化
- template<>
- class TMP<int, float, 2, cp00>;
- // 第一个参数有const修饰
- template<typename T1, typename T2, int i, template<typename, int> class CP>
- class TMP<const T1, T2, i, CP>;
- // 第一二个参数为cp00的实例且满足一定关系,第四个参数为cp00
- template<typename T, int i>
- class TMP<cp00<T, i>, cp00<T, i+10>, i, cp00>;
- // 编译错误!,第四个参数类型和通例类型不一致
- //template<template<int i> CP>
- //class TMP<int, float, 10, CP>;
复制代码 关于模板特例化(详见文献[1]第4章):
- 在定义模板特例之前必须已经有模板通例(primary template)的声明;
- 模板特例并不要求一定与通例有相同的接口,但为了方便使用(体会特例的语义)一般都相同;
- 匹配规则,在模板实例化时如果有模板通例、特例加起来多个模板版本可以匹配,则依据如下规则:对版本AB,如果 A 的模板参数取值集合是B的真子集,则优先匹配 A,如果 AB 的模板参数取值集合是“交叉”关系(AB 交集不为空,且不为包含关系),则发生编译错误,对于函数模板,用函数重载分辨(overload resolution)规则和上述规则结合并优先匹配非模板函数。
对模板的多个实例,类型等价(type equivalence)判断规则(详见文献[2] 13.2.4):同一个模板(模板名及其参数类型列表构成的模板签名(template signature)相同,函数模板可以重载,类模板不存在重载)且指定的模板实参等价(类型参数是等价类型,非类型参数值相同)。
如下例子:
- #include <iostream>
- // 识别两个类型是否相同,提前进入模板元编程^_^
- template<typename T1, typename T2> // 通例,返回 false
- class theSameType { public: enum { ret = false }; };
- template<typename T> // 特例,两类型相同时返回 true
- class theSameType<T, T> { public: enum { ret = true }; };
- template<typename T, int i> class aTMP { };
- int main(){
- typedef unsigned int uint; // typedef 定义类型别名而不是引入新类型
- typedef uint uint2;
- std::cout << theSameType<unsigned, uint2>::ret << '\n';
- // 感谢 C++11,连续角括号“>>”不会被当做流输入符号而编译错误
- std::cout << theSameType<aTMP<unsigned, 2>, aTMP<uint2, 2>>::ret << '\n';
- std::cout << theSameType<aTMP<int, 2>, aTMP<int, 3>>::ret << '\n';
- std::cin.get(); return 0;
- }
复制代码
|
|