template metaprogram
metaprogramming含有“对一个程序进行编程”的意思,换句话说,编程系统会执行我们所写的代码,来生成新的代码,而这些新代码才正真体现了我们所期望的功能。通常而言,metaprogramming这个概念意味着一种发射的特性:metaprogramming组件只是程序的一部分,而且它也只生一部分代码或者程序。我们为什么需要metaprogramming呢?和大多数程序设计技术一样,使用metaprogramming的目的是为了实现更多的功能,并且使花费的开销更小,其中开销是以:代码大小、维护的开销等来衡量的。另一方面,metaprogramming的最大特点在于:木屑用户自定义的计算可以在程序翻译期进行。而这通常都能够在性能(因为在程序翻译期所进行的计算通常都可以被优化或者接口简单性(一个metaprogramming通常都要比它所扩展的程序简短)方面带来好处;甚至为两方面都带来好处。下面来看一个简单metaprogramming程序,该程序的功能为实现3的幂:
//pow3.hpp #ifndef POW3_HPP #define POW3_HPP template<int N> class Pow3 { public: enum { result = 3 * Pow3<N-1>::result }; }; template<> class Pow3<0> { public: enum { result = 1 }; }; #endif // POW3_HPP //pow3.cpp #include <iostream> #include "pow3.hpp" int main() { std::cout << "Pow3<7>::result = " << Pow3<7>::result << std::endl; return 0; }
该template metaprogramming所做的工作就是递归的模板实例化。在这个计算3的N次幂的递归模板实例化
将应用下面两个规则:
1. 3的N次幂 = 3*3的(N-1)次幂
2. 3的0次幂等于1
有的人可能会对上述程序的结果保存在enum中有疑问?其实这么做是有原因的:
在原来的C++编译器中,在类声明的内部,枚举值是声明“真常值”(也称为常量表达式“的唯一方法)。
然而,现在的情况已经发生了改变,C++的标准化过程引入了在类内部进行静态常量初始化的概念。例如:
struct TrueConstants
{
enum{Three = 3};
static int const Four = 4;
};
其中,Four就是一个“真常量”,和Three一样。
但是我们除了这个原因以外,我们使用枚举值而不使用静态成员变量还有另一个原因:
静态成员变量只能是左值,而枚举值却不是左值(也就是说,它没有地址)
因此,如果上述程序使用静态成员变量,且含有如下声明:
void foo(int const&);
当我们将上一个metaprogram的结果传递进去后:
即foo(Pow<7>::result);
那么编译器必须传递Pow3<7>::result的地址,而这会强制编译器实例化静态成员的定义,并为该定义
分配内存,因此,该计算将步子局限与完全的“编译期”效果。而枚举值不是左值(没有地址)。
因此,当你通过引用传递枚举值的时候,并不会使用任何静态内存,就像使用以文字常量的形式
传递这个完成计算的值一样。
下面我们看metaprogramming的第二个例子:计算平方根(取一个数平方根的上限)
metaprogramming实现1(使用二分查找技术):
//ifthenelse.hpp #ifndef INTHENELSE_HPP #define INTHENELSE_HPP template<bool b,typename T1,typename T2> class IfThenElse; template<typename T1,typename T2> class IfThenElse<true,T1,T2> { public: typedef typename T1 ResultT; }; template<typename T1,typename T2> class IfThenElse<false,T1,T2> { public: typedef typename T2 ResultT; }; #endif //sqrt.hpp #ifndef SQRT_HPP #define SQRT_HPP #include "ifthenelse.hpp" template<int N, int LO=1, int HI=N> class Sqrt { public: enum { mid = (LO+HI+1)/2 }; typedef typename IfThenElse<(N<mid*mid), Sqrt<N,LO,mid-1>, Sqrt<N,mid,HI> >::ResultT SubT; enum { result = SubT::result }; }; template<int N, int S> class Sqrt<N, S, S> { public: enum { result = S }; }; #endif //sqrt.cpp #include <iostream> #include "sqrt.hpp" using namespace std; int main() { std::cout << "Sqrt<16>::result is : " << Sqrt<16>::result << std::endl; std::cout << "Sqrt<25>::result is : " << Sqrt<25>::result << std::endl; std::cout << "Sqrt<42>::result is : " << Sqrt<42>::result << std::endl; return 0; }
metaprogramming实现2(使用归纳变量):
//sqrt.hpp #ifndef SQRT_HPP #define SQRT_HPP #include "ifthenelse.hpp" template<int N> class Value { public: enum { result = N }; }; template <int N, int I=1> class Sqrt { public: typedef typename IfThenElse<(I*I<N), Sqrt<N,I+1>, Value<I> >::ResultT SubT; enum { result = SubT::result }; }; #endif //sqrt.cpp #incldue <iostream> #include "sqrt4.hpp" int main() { std::cout << "Sqrt<16>::result = " << Sqrt<16>::result << std::endl; std::cout << "Sqrt<25>::result = " << Sqrt<25>::result << std::endl; std::cout << "Sqrt<42>::result = " << Sqrt<42>::result << std::endl; std::cout << "Sqrt<1>::result = " << Sqrt<1>::result << std::endl; return 0; }
在上述程序中我们使用了IfThenElse模板,其实这个模板的功能和三元运算符 ? : 实现的功能是完全
一样的,但是为什么我们还不辞辛苦的再写一遍呢?当然,这是有原因的,如果我们在上述程序使用三元
运算符 ? : ,当编译器试图计算下面表达式的时候:
(16<=8*8) ? Sqrt<16,1,8>::result : Sqrt<16,9,16>::result
编译器不仅实例化位于条件运算符正面分支的模板(即,Sqrt<16,1,8>),同时也实例化了负面分支的模
板(Sqrt<16,9,16>)。而且,由于代码试图使用::运算符方位结果类的成员result,所以类中的所有成
员也会被实例化。这就意味着:
完全实例化Sqrt<16,9,16>将会促使Sqrt<16,9,12>和Sqrt<16,13,16>的完全实例化。最后,当我们仔细考
察整个过程的时候,我们会发现最终将会产生数量庞大的实例化体,总数大约是N的两倍。所以我们使用
IfThenElse模板来代替三元运算符 ? : ,当我们编写
typedef typename IfThenElse<(N<mid*mid),Sqrt<N,LO,mid-1>,Sqrt<N,mid,HI> >::ResultT SubT;的时
候,Sqrt<N,LO,mid-1>和Sqrt<N,mid,HI>都不会被完全实例化。实际上,SubT最后只能代表其中的一个类
型,而且只有在查找SubT::result的时候,才会被完全实例化SubT所代表的类型。基于这种策略,我们的
实例化体的数量趋向于log2(N)。
通过上述两个程序我们可以看出:一个template metaprogram包含以下几部分:
1.状态变量:也就是模板参数
2.迭代构造:通过递归
3.路径选择:通过使用条件表达式或者特化
4.整形(即枚举里面的值应该为整形)算法