C++模板元编程之使用模板,对数组进行“编译期间求和”

来源:互联网 发布:房产销售软件 编辑:程序博客网 时间:2024/06/07 14:43

编译期间求值,将计算提前到编译期间进行,可以最大限度地榨干编译器的潜力,提高程序的运行速度,用Andrei Alexanderescu的话说就是“时间花在编译期,就某种意义来说这是‘免费的’”(《C++设计新思维》P55)。所以就有了对数组在编译期间求和的需求。

先上代码,再解释:

#include <iostream>using namespace std;//一个全局的int数组,需要对其求和const int CONST_ARRAY[5]={1,2,3,4,5};//去掉const属性后,编译也可以通过//一个类模板,用于对数组求和template<int index>struct SumArrInCompilePhaseCls{static const int SUM;};//通过递归定义模板的方式求SUM的值template<int index>const int SumArrInCompilePhaseCls<index>::SUM=CONST_ARRAY[index]+SumArrInCompilePhaseCls<index-1>::SUM;//亮点在这里//一个完全特化的类模板,用于结束模板的递归定义template<>struct SumArrInCompilePhaseCls<0>{static const int SUM;};const int SumArrInCompilePhaseCls<0>::SUM=CONST_ARRAY[0];int main(){        //使用模板,进行编译期间的数组求和cout<<SumArrInCompilePhaseCls<4>::SUM<<endl;//结果15}
SUM确实是在编译阶段计算的,因为SUM是static const类型,该类型在编译完成之后,就固定了,无法在运行期间改变!

仔细研究上面一段代码,就会发现,在定义非特化类模板的SUM值时,使用了模板的递归定义:

//通过递归定义模板的方式求SUM的值template<int index>const int SumArrInCompilePhaseCls<index>::SUM=CONST_ARRAY[index]+SumArrInCompilePhaseCls<index-1>::SUM;
编译器在编译该表达式时,由于SumArrInCompilePhaseCls<index-1>类型未知,所以编译器会生成SumArrInCompilePhaseCls<index-1>,继而生成SumArrInCompilePhaseCls<index-2>,SumArrInCompilePhaseCls<index-3>,……还有好多~。也就是说,该表达式,导致编译器递归生成多个SumArrInCompilePhaseCls模板类,而当index终于递减到0时,即要生成SumArrInCompilePhaseCls<0>时,编译器“惊喜”地发现,SumArrInCompilePhaseCls<0>已经实现了!所以编译器就停止继续生成"SumArrInCompilePhaseCls模板类"了,递归结束了!

由于所有“SumArrInCompilePhaseCls模板类”里的SUM都是static const值,所以对于const值,就可以在编译期间求和。


结束了吗?

木有!

上面的代码有两个小问题:

1、调用时,输入的index如果为负数(比如-1),编译期间出现递归栈溢出,编译失败!因为负数无法“递减到0",也就是说,递归不会停止!

2、输入的index超过CONST_ARRAY的上限时(比如5),尽管编译通过,但是运行结果错误,因为编译期间求和的时候数组越界了!

好了,我们希望对数组求和的时候,可以判断用户的输入是否合法!

可以通过增加一个“编译期间的断言”来解决这两问题,这个断言的源码如下:

//一个编译期间起断言作用的类模板,用于保证数组下标始终非负template<bool isOk>struct CompilePhaseAssertion;template<>struct CompilePhaseAssertion<true>{};

使用的时候,可以这样子:

//一个类模板,用于对数组求和template<int index>struct SumArrInCompilePhaseCls{static const int SUM;private:CompilePhaseAssertion<index>=0&&index<sizeof(CONST_ARRAY)/sizeof(int)> IsValidIndex;};
原理很明显了吧?

CompilePhaseAssertion是个类模板,但是并没有定义它!仅仅有一个参数为true的完全特化版本,所以,当模板参数isOk为false时,根本无法生成这种类型的实例,因为没有isOk==false的模板类,因而导致编译报错。

通过在CompilePhaseAssertion中增加CompilePhaseAssertion实例的方式,保证index的范围合法。

修改后的代码如下:

#include <iostream>using namespace std;//一个全局的int数组,需要对其求和int CONST_ARRAY[5]={1,2,3,4,5};//一个编译期间起断言作用的类模板,用于保证数组下标始终非负template<bool isOk>struct CompilePhaseAssertion;template<>struct CompilePhaseAssertion<true>{};//一个类模板,用于对数组求和template<int index>struct SumArrInCompilePhaseCls{static const int SUM;private:CompilePhaseAssertion<index>=0&&index<sizeof(CONST_ARRAY)/sizeof(int)> IsValidIndex;};//通过递归定义模板的方式求SUM的值template<int index>const int SumArrInCompilePhaseCls<index>::SUM=CONST_ARRAY[index]+SumArrInCompilePhaseCls<index-1>::SUM;//一个完全特化的类模板,用于结束模板的递归定义template<>struct SumArrInCompilePhaseCls<0>{static const int SUM;};const int SumArrInCompilePhaseCls<0>::SUM=CONST_ARRAY[0];int main(){//使用模板,进行编译期间的数组求和cout<<SumArrInCompilePhaseCls<4>::SUM<<endl;}

此时,如果使用以下两种方式求和,则编译报错:

cout<<SumArrInCompilePhaseCls<-1>::SUM<<endl;//数组下标为负cout<<SumArrInCompilePhaseCls<5>::SUM<<endl;//数组上界越界


正文完。


参考资料:

1、《C++设计新思维》,Andrei Alexanderescu著,侯捷、於春景译。

2、C++模板元编程技术与应用

3、荣威老师写的其他文章或书籍

4、C++ 的MetaProgramming 入门篇

注意:

以上代码在VS2005和G++中都能编译通过,但是只有G++编译出来的程序才能运行出正确结果(15),VS2005中的结果是错误的(5),原因还不清楚,如果您知道VS2005计算错误的原因,请及时留言通知我,我会非常感激您的!



原创粉丝点击