实验平台:Win7,VS2013 Community,GCC 4.8.3(在线版)
所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得异常灵活,能实现很多高级动态语言才有的特性(语法上可能比较丑陋,一些历史原因见下文)。普通用户对 C++ 模板的使用可能不是很频繁,大致限于泛型编程,但一些系统级的代码,尤其是对通用性、性能要求极高的基础库(如 STL、Boost)几乎不可避免的都大量地使用 C++ 模板,一个稍有规模的大量使用模板的程序,不可避免的要涉及元编程(如类型计算)。本文就是要剖析 C++ 模板元编程的机制。
下面所给的所有代码,想做实验又懒得打开编译工具?一个在线运行 C++ 代码的网站(GCC 4.8)很好~
(本博文地址:http://www.cnblogs.com/liangliangh/p/4219879.html,转载版本将得不到作者维护)
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
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;
}
1
1
0
关于模板实例化(template instantiation)(详见文献[4]模板):
指在编译或链接时生成函数模板或类模板的具体实例源