c++模板元编程
2015-02-27 12:51 youxin 阅读(651) 评论(0) 编辑 收藏 举报
范例:
// 主模板 template<int N> struct Fib { enum { Result = Fib<N-1>::Result + Fib<N-2>::Result }; }; // 完全特化版 template <> struct Fib<1> { enum { Result = 1 }; }; // 完全特化版 template <> struct Fib<0> { enum { Result = 0 }; }; int main() { int i = Fib<10>::Result; // std::cout << i << std::endl; }
主要思想
利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。
优劣及适用情况
通过将计算从运行期转移至编译期,在结果程序启动之前做尽可能多的工作,最终获得速度更快的程序。也就是说模板元编程的优势在于:
1.以编译耗时为代价换来卓越的运行期性能(一般用于为性能要求严格的数值计算换取更高的性能)。通常来说,一个有意义的程序的运行次数(或服役时间)总是远远超过编译次数(或编译时间)。
2.提供编译期类型计算,通常这才是模板元编程大放异彩的地方。
模板元编程技术并非都是优点:
1.代码可读性差,以类模板的方式描述算法也许有点抽象。
2.调试困难,元程序执行于编译期,没有用于单步跟踪元程序执行的调试器(用于设置断点、察看数据等)。程序员可做的只能是等待编译过程失败,然后人工破译编译器倾泻到屏幕上的错误信息。
3.编译时间长,通常带有模板元程序的程序生成的代码尺寸要比普通程序的大,
4.可移植性较差,对于模板元编程使用的高级模板特性,不同的编译器的支持度不同。
总结:
模板元编程技术不适用普通程序员的日常应用,它常常会做为类库开发的提供技术支持,为常规模板代码的内核的关键算法实现更好的性能或者编译期类型计算。模板元程序几乎总是应该与常规代码结合使用被封装在一个程序库的内部。对于库的用户来说,它应该是透明的。
工程应用实例
1. Blitz++:由于模板元编程最先是因为数值计算而被发现的,因此早期的研究工作主要集中于数值计算方面,Blitz++库利用模板将运行期计算转移至编译期的库,主要提供了对向量、矩阵等进行处理的线性代数计算。
2.Loki:将模板元编程在类型计算方面的威力应用于设计模式领域,利用元编程(以及其他一些重要的设计技术)实现了一些常见的设计模式之泛型版本。Loki库中的Abstract Factory泛型模式即借助于这种机制实现在不损失类型安全性的前提下降低对类型的静态依赖性。
3.Boost:元编程库目前主要包含MPL、Type Traits和Static Assert等库。 Static Assert和Type Traits用作MPL的基础。Boost Type Traits库包含一系列traits类,用于萃取C++类型特征。另外还包含了一些转换traits(例如移除一个类型的const修饰符等)。Boost Static Assert库用于编译期断言,如果评估的表达式编译时计算结果为true,则代码可以通过编译,否则编译报错。
技术细节
模板元编程使用静态C++语言成分,编程风格类似于函数式编程,在模板元编程中,主要操作整型(包括布尔类型、字符类型、整数类型)常量和类型,不可以使用变量、赋值语句和迭代结构等。被操纵的实体也称为元数据(Metadata),所有元数据均可作为模板参数。
由于在模板元编程中不可以使用变量,我们只能使用typedef名字和整型常量。它们分别采用一个类型和整数值进行初始化,之后不能再赋予新的类型或数值。如果需要新的类型或数值,必须引入新的typedef名字或常量。
其它范例
// 仅声明 struct Nil; // 主模板 template <typename T> struct IsPointer { enum { Result = false }; typedef Nil ValueType; }; // 局部特化 template <typename T> struct IsPointer<T*> { enum { Result = true }; typedef T ValueType; }; // 示例 int main() { cout << IsPointer<int*>::Result << endl; cout << IsPointer<int>::Result << endl; IsPointer<int*>::ValueType i = 1; //IsPointer<int>::ValueType j = 1; // 错误:使用未定义的类型Nil }
//主模板 template<bool> struct StaticAssert; // 完全特化 template<> struct StaticAssert<true> {}; // 辅助宏 #define STATIC_ASSERT(exp)\ { StaticAssert<((exp) != 0)> StaticAssertFailed; } int main() { STATIC_ASSERT(0>1); }
求和:
#include <iostream> using namespace std; template<int N> class Sum { public: enum {value = N + Sum<N-1>::value }; }; template<> class Sum<0> { public: enum {value = 0}; }; int main() { cout << Sum<100>::value << endl; }
一个例子:从1输出100:
http://www.cnblogs.com/youxin/p/4098110.html
无非也就是个递归,就像写普通的递归函数一样,写上递归规律(模板函数)和递归结束条件(模板函数特化),但当我把传入的值换成501时(gcc4.1?),编译器就报错了,从出错信息判断貌似编译器对模板递归有限制,后来在gcc 4.6上试了一下,他的上限是1024,而且错误信息比较靠谱:
sum.cpp:9:14: error: template instantiation depth exceeds maximum of 1024 (use -ftemplate-depth= to increase the maximum) instantiating ‘class Sum<1>’ sum.cpp:9:14: recursively instantiated from ‘Sum<1024>’ sum.cpp:9:14: instantiated from ‘Sum<1025>’ sum.cpp:22:22: instantiated from here
那就用-ftemplate-depth吧,当然能增大,但是毕竟系统资源有限,你不能无节制的往上加。编译器有此限制当然有其原因:
- 资源 - 每一个递归都要保存状态,内存迟早爆掉
- 有些写的不好的模板类,可能是无限递归 - 要不设限,编译器就hang在那边了
对于Windows下VC9,其默认限制是499(到500就挂了),查看了下编译器参数,没发现有增大最大深度的。
这事也就完了,但有人提出了个用metaprogramming求解sum的好办法(当然不是用N(N+1)/2),蛮有灵感的:
#include <iostream> using namespace std; template<unsigned Start, unsigned End> struct adder{ static unsigned const Middle = (Start + End) / 2; static unsigned const value = adder<Start, Middle>::value + adder<Middle+1, End>::value; }; template<unsigned End> struct adder<End, End>{ static unsigned const value = End; }; int main() { cout << adder<1, 1000>::value; }
稍微懂点算法的人,看一下就知道这是用了分治法,每次递归都是两个分叉,这样就把总数度从N降到了LgN,巨大的提高啊!
其实,所谓的编译期运算,对编译器来讲,就是运行期,所以写递归算法也好,改进递归用分治也好,和写普通代码都是一样的道理。