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.整形(即枚举里面的值应该为整形)算法

posted @ 2011-11-14 20:17  MagiCube  阅读(442)  评论(0编辑  收藏  举报