logo

当前栏目:社区首页->软件开发->windows应用开发 转到:在该栏目发表文章社区后台管理搜索
template Meta-programming 介绍
作者: keven 日期: 07-03-06, 20:20
I.背景 

C  在很多人的心目中,一直是一种OO语言,而事实上,现在对C  的非OO部分的各种使
用被逐渐地挖掘出来,其中最大的部分莫过于是template。STL、loki、boost,...,很
多先行者为我们提供了方案,有的已经被列入C  标准的一部分。template的一个重要使
用方法就是template meta programming,它利用编译器对于template的解释是静态的这
一特性,让编译器在编译时做计算,可以有效的提高程序的运行速度。有关于template 
meta programming的记载,最早见于Erwin Unruh,他在1994年写了一个用template计算
质数的程序。我希望通过这篇文章介绍一些TMP的基本技巧和应用,并且最终完成一个质
数计算程序。 
(阅读本文的过程中,建议你试图编译每一个给出的程序。由于所有的类只需要public
成员,所以都用struct声明,但是仍然称之为类。) 

II.技术 

通常我们编写一个(小)程序,需要的语言支持其实不必很多,只要有顺序、选择和循
环三种控制结构理论上就可以写出大多数程序了。我们先用TMP建立一个简单的语言环境
。 

1.打印 

程序有了结果,需要有一个方式反馈给运行者,这里我们利用C  的出错信息,建立一个
打印函数。要知道我们希望一切都在编译的时候结束,那么我们就必须让C  编译器在编
译信息里面告诉我们,所以我们利用编译器的出错信息。当然这只是一个trick,如果你
的TMP只是程序的一部分,你可以使用正常的输入输出。 


Code:template 
struct print 
{ 
   static const unsigned int result = (unsigned char*)value; 
};


这个类,每当别人引用到它的result的时候,编译器就会打印出错信息,因为一个unsig
ned int是不能隐式的转成一个unsigned char*的。譬如下面这段程序 


Code:template 
struct print 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

unsigned int test1 = print<77>::result; 
unsigned int test2 = print<123>::result;


在我的Dev C  里,会输出 


Code:main.cpp: In instantiation of `print<77>': 
main.cpp:7:   instantiated from here 
main.cpp:4: invalid conversion from `unsigned char*' to `unsigned int' 
main.cpp: In instantiation of `print<123>': 
main.cpp:8:   instantiated from here 
main.cpp:4: invalid conversion from `unsigned char*' to `unsigned int'


这个输出虽然不是很好看,但也算是差强人意。 

2.选择 

Andrei Alexanderescu在他的大作Modern C   Design里面使用过一个类,可以根据bool
的值选择不同的类型。今天我们要写的一个是根据bool的值选择不同的整数。 


Code:template 
struct template_if 
{ 
   static const unsigned int result = value1; 
}; 

template 
struct template_if 
{ 
   static const unsigned int result = value2; 
};


这里用到了模板的特化,如果你对这个不熟悉,那么大致可以这样理解:第一个templat
e_if的定义告诉编译器,“一般的”template_if,会选择第一个值作为结果。第二个te
mplate_if告诉编译器,如果第一个参数是false的话,我们就使用第二个值(第三个参
数)作为结果。下面这段代码演示了template_if的用法。 


Code:template 
struct print 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct template_if 
{ 
   static const unsigned int result = value1; 
}; 

template 
struct template_if 
{ 
   static const unsigned int result = value2; 
}; 

template 
struct print_if_77 
{ 
   static const unsigned int result = template_if::result , 0>::result; 
}; 

unsigned int test1 = print_if_77<77>::result; 
unsigned int test2 = print_if_77<123>::result;


如果你去编译这段代码的话,你会发觉77和123都被打印出来了,虽然错误信息不一样,
但是这不是我们想要的结果。为什么呢?很遗憾,对C  编译器来说,template_if和template是两个不同的类,虽然后一个参数的值我们并不关
心,但是编译器必须在template初始化的时候,给出所有的参数,这就导致它会去计算p
rint::result,当然,计算的结果就是报错。也就是说,因为编译器要计算这个
值才导致了我们的print不可用,要解决这个问题,有两个方法:或者让编译器不计算这
个值,或者让编译器在某些情况下可以计算出正确的值。 

方法一可以让编译器不计算这个值,通过修改template_if,我们传入两个不同的类,而
不是unsigned int。 
首先修改print,加一个新的类dummy_print: 


Code:template 
struct print 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct dummy_print 
{ 
   static const unsigned int result = value; 
};


接着,加入一套对类型进行选择的模板: 


Code:template 
struct template_if_type 
{ 
   static const unsigned int result = T1::result; 
}; 

template 
struct template_if_type 
{ 
   static const unsigned int result = T2::result; 
};


这样原先的程序就变成: 


Code:template 
struct print 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct dummy_print 
{ 
   static const unsigned int result = value; 
}; 

template 
struct template_if_type 
{ 
   static const unsigned int result = T1::result; 
}; 

template 
struct template_if_type 
{ 
   static const unsigned int result = T2::result; 
}; 

template 
struct print_if_77 
{ 
   static const unsigned int result = template_if_type , dummy_print >::result; 
}; 

unsigned int test1 = print_if_77<77>::result; 
unsigned int test2 = print_if_77<123>::result;


现在的“运行结果”非常正确。 

方法二可以让编译器在某些情况下计算出正确的值,我们加一套新的模板: 


Code:template 
struct print_if 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct print_if 
{ 
   static const unsigned int result = value; 
};


原先的程序变为: 


Code:template 
struct print_if 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct print_if 
{ 
   static const unsigned int result = value; 
}; 

template 
struct print_if_77 
{ 
   static const unsigned int result = print_if::result; 
}; 

unsigned int test1 = print_if_77<77>::result; 
unsigned int test2 = print_if_77<123>::result;


输出也是正确的。 

这两种方案,我个人倾向于后者,因为其实我们一定是要做一次判断的,并且这次判断
一定会添加新的类,那么还是print_if的解决方案比较直观。 

3. 循环 

首先必须明确的是,template不可能实现我们一般意义上的循环,但是它可以做一件和
循环类似的事情:迭代。 
如果有这样一个循环:for( unsigned int i = 0 ; i < value ;   i ) 
我们可以这样写: 


Code:template 
struct loop 
{ 
   static const unsigned int result = loop::result   1; 
}; 

template<> 
struct loop<0> 
{ 
   static const unsigned int result = 0; 
};


这就是告诉编译器,我们的迭代从0开始,到value结束,每个值是前者加1。 
下面给出一个更广泛的循环的实现:for( unsigned int i = begin ; i < end ; i = 
i   step ),假设0<=begin0。(更复杂的情况,总可以通过template 
specialization分派完成) 


Code:template 
struct loop 
{ 
   static const unsigned int result = loop< begin   step, end, 
step>::result - step; 
}; 

template 
struct loop 
{ 
   static const unsigned int result = begin; 
};


这里的result的计算过程不重要,关键是为了驱动编译器进一步的实例化模板。 
下面是一个实例程序,用来打印13到29之间的整数,步长为5。 


Code:template 
struct print_if 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct print_if 
{ 
   static const unsigned int result = value; 
}; 

template 
struct loop 
{ 
   static const unsigned int result = loop< begin   step, end, 
step>::result - step; 
   static const unsigned int print_result = print_if::result; 
}; 

template 
struct loop 
{ 
   static const unsigned int result = begin; 
}; 

static unsigned int result = loop<13,29,5>::result;


III.应用 

上面我已经介绍了怎样用TMP实现打印,选择和循环了,现在我们来把这些投入运用。下
面我会用上面的所提供的机制,写两个程序:计算阶乘和计算质数。 

1.计算阶乘 

我们先写一个普通的C  程序来计算阶乘: 


Code:#include  

int main() 
{ 
   unsigned int limit = 10; 
   unsigned int factorial = 1; 
   for( unsigned int i = 1 ; i <= limit ;    i ) 
      factorial *= i; 
   std::cout< 
struct print 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct loop 
{ 
   static const unsigned int result = loop< begin   step, end, 
step>::result * begin; 
}; 

template 
struct loop 
{ 
   static const unsigned int result = begin; 
}; 

static unsigned int result = print::result>::result;


因为这里不必要盘算是否输出,所以就直接用print了。 

2.计算质数 

同样,我们先写一个普通的程序来计算: 


Code:#include  

int main() 
{ 
   unsigned int limit = 30; 
   for( unsigned int i = 2 ; i <= limit ;   i ) 
   { 
      unsigned int j; 
      for( j = 2 ; j < i ;   j ) 
         if( i % j == 0 ) 
            break; 
      if( i == j ) 
         std::cout< 
struct print_if 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct print_if 
{ 
   static const unsigned int result = value; 
}; 

template 
struct template_if 
{ 
   static const unsigned int result = value1; 
}; 

template 
struct template_if 
{ 
   static const unsigned int result = value2; 
}; 

// 这里增加一个i作为参数,因为在内循环也需要知道外部的i的值 

template 
struct inner_loop 
{ 
   static const unsigned int result = template_if::result, 
      begin>::result; 
}; 

template 
struct inner_loop 
{ 
   static const unsigned int result = begin; 
}; 

template 
struct outer_loop 
{ 
   static const unsigned int result = outer_loop< begin   step, end, 
step>::result; 
   static const unsigned int is_prime = inner_loop::result == begin; 
   static const unsigned int print_result = print_if::result; 
}; 

template 
struct outer_loop 
{ 
   static const unsigned int result = 0; 
}; 

static unsigned int result = outer_loop<2, 30, 1>::result;


III.细节 

另外有两点要说一下: 
我们的template_if其实有一种更简单的写法,就是?:表达式。 
而我们的print_if和print其实可以用确省的模板参数来统一,唯一的区别是,要把valu
e放在condition前面。 


Code:template 
struct print 
{ 
   static const unsigned int result = (unsigned char*)value; 
}; 

template 
struct print 
{ 
   static const unsigned int result = value; 
};


这样你可以用print来打印一个数值,也可以用print来做判
断打印。 

IIII.后记 

很久以前看Inside OLE2的时候,记得作者说过一句话:作者因为写书而明白。我其实几
年前就写过类似的程序,但是从来没有对这样程序的写法进行过总结以至于每一次都是
在重新开始。而写完这篇文章后,我觉得自己比过去明白很多。template meta 
programming还有很多不同的应用,我以后有机会会继续介绍给大家。 
对于这篇文章有任何问题,请发信到polyrandom@hotmail.com和我联系,也请访问http:
//www.allaboutprogram.com/以获得最近的更新。 

文章版权归原作者所有! (www.MegaEntry.com)



回复
标题: 

强烈建议采用IE 6.0或以上的浏览器