模板元编程是编写基于模板的C++程序并执行于编译期的过程。即模板元编程是以C++写成、执行于C++编译器内的程序。一旦模板元编程程序结束执行,其输出,也就是从模板具现出来的若干C++源码,便会一如既往地被编译。
模板元编程有两个伟大的效力。第一,它让某些事情更容易。如果没有它,那些事情将是困难的,甚至是不可能的。第二,由于模板元编程执行于C++编译期,因此可将工作(如:错误检查)从运行期转移到编译器。还能让C++程序在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。
看前一篇blog中的代码:
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
if(iter is a random access iterator)
iter += d;
else
{
if(d >= 0){while(d--) ++iter;}
else {while(d++) --iter;}
}
}
我们可以使用typeid让其中的伪码成真,取得C++对此问题的一个“正常”解决方案——所有工作都在运行期进行:
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
if(typeid(typename std::iterator_traits<IterT>::iterator_catagory)
== typeid(std::random_access_iterator_tag))
iter += d;
else
{
if(d >= 0){while(d--) ++iter;}
else {while(d++) --iter;}
}
}
在上一篇blog中说过,这个typeid-based解法的效率比traits解法低,因为在此方案中(1)类型测试发生于运行期而非编译期,(2)“运行期类型测试”代码会出现在(或被链接于)可执行文件中。实际上这个例子正可彰显模板元编程如何能够比“正常的”C++程序更高效,因为traits解法就是模板元编程。
在上一篇中说到advance的typeid-based实现方式可能导致编译期问题,下面就是例子:
list<int>::iterator iter;
...
advance(iter,10); //移动iter向前走10个元素,这个代码无法通过编译
下面这一版advance便是针对上述调用而产生的。将模板参数IterT和DistT分别替换为iter和10的类型之后,我们得到:
void advance(list<int>::iterator& iter,int d)
{
if(typeid(typename std::iterator_traits<list<int>::iterator>::iterator_catagory)
== typeid(std::random_access_iterator_tag))
iter += d;
else
{
if(d >= 0){while(d--) ++iter;}
else {while(d++) --iter;}
}
}
问题出在+=操作符,那是尝试在一个list<int>::iterator身上使用+=,但list<int>::iterator是双向迭代器,并不支持+=。只有随机访问迭代器才支持+=。此刻我们绝不会执行起+=那一行,因为测试typeid的那一行总是会因为list<int>::iterator而失败,但编译器必须确保所有源码有效,纵使是不会执行起来的代码!而当iter不是随机访问迭代器时“iter+=d”无效。与此对比的是基于traits模板元编程解法,其针对不同类型而进行的代码,被拆分为不同的函数,每个函数所使用的操作(操作符)都可施行于该函数所对付的类型。
模板元编程(TMP)已被证明是个“图灵完全”机器,意思是它的威力大到足以计算任何事物,使用TMP可以声明变量、执行循环、编写及调用函数。
让我们看看TMP中的循环。TMP并没有真正的循环构件,所以循环效果由递归完成。TM主要是个“函数式语言”。TMP的递归甚至不是正常种类,因为TMP循环并不涉及递归函数调用,而是涉及“递归模板具现化”。
TMP的阶乘运算示范如何通过“递归模板具现化”实现循环,以及如何在TMP中创建和使用变量。
template<unsigned n>
struct Factorial //一般情况:Factorial<n>的值是n乘以Factorial<n-1>的值
{
enum{value = n * Factorial<n-1>::value};
};
template<>
struct Factorial<0> //特殊情况:Factorial<0>的值是1
{
enum{value = 1};
};
有了这个模板元编程(其实只是个单一的模板元函数Factorial),只要你指涉Factorial<n>::value就可以得到n阶乘的值。
循环发上在模板具现体Factorial<n>内部指涉另一个模板具现体Factorial<n-1>之时。和所有良好递归一样,我们需要一个特殊情况造成递归结束,这里的特殊情况是template特化体Factorial<0>。
每个Factorial模板具现体都是一个struct,每个struct都使用enum hack声明一个名为value的TMP变量,vaule用来保存当前计算所得的阶乘值。如果TMP拥有真正的循环构件,value应该在每次循环内获得更新。但由于TMP系以“递归模板具现化”取代循环,每个具现体有自己的一份value,而每个value有其循环内的适当值。
现在可以这样使用Factorial(VC++6.0测试通过):
int main()
{
cout<<Factorial<5>::value; //打印出120
cout<<Factorial<10>::value; //打印出3628800
}
TMP能够达到的目标:
(1)确保亮度单位正确。如可确保将距离除以时间的结果给速度等。
(2)优化矩阵计算。例如能够减少临时矩阵的生成等。
(3)可以生成客户定制之设计模式实现品。
总结:(1)TMP可将工作由运行期移往编译器,因而得以实现早期错误侦查和更高的执行效率。
(2)TMP可被用来生成“基于政策选择组合”的客户定制代码,也可以用来避免生成对某些特殊类型并不适合的代码。
|