关于模板元编程的知识也有所了解,相关的书籍也看过几本,但是至今还没有亲手写过一个模板元程序,原因就是没有一个合适的机会应用模板元编程技术,今天在CSDN上看见一个帖子,定义常量字符串
char *p="Hello,Word!";
既然是常量字符串,应该可以在编译期知道p的长度,在编译期间如何得到?
这个问题,我首先想到用模板元编程来实现,于是尝试编写一个。
真正用模板元编程时,才发现自己完全没有思路,不知道如何解决。于是下定决心学好模板元编程,这个问题就是我学习的动力,希望以后能够使用模板元编程解决这个问题,或者明确下来不能解决这个问题。
今天写了个例子,才发现自己对模板元的理解很肤浅,常常被一些弱智的问题打败,对于任何想新学模板元编程的同学可能都会碰到,记录下来和大家分享一下,希望对别人有所帮助。
我们从模板元编程的"Hello
word"开始,使用模板元编程求一个数的阶乘。这是我写的第一个例子
#include using namespace::std; template class B { public: int num() { return I * B().num(); } }; template<> class B <1>{ public: int num() { return 1; } }; int main() { int i = B<5>().num(); cout << i << endl; }
编译运行,输出正确结果,啊哈,这么简单。但是很遗憾,这不是一个模板元编程,而是一个泛型编程而已。模板元编程是运用编译器在编译阶段程序设计,而上面是类实例,通过调用对象方法来实现的,大忌啊!所以要仔细区分编译期和运行期的区别,下面有必要列举C++中常用编译期操作和运行期操作。
编译期
宏
typedef影射
static类型变量和函数
const 类型变量
=,:?,-运算符
enum
运行期
对象使用
函数调用
变量赋值
操作变量时&,+=,++,--等运算符。
所以,如果想实现模板元编程,必须要把握的是一定要在编译期完成程序,而不是在程序的运行期,仔细区分运行期和编译期是模板元编程的第一步。
那么根据以上要求,一种比较正确的方法是,在下面的例子中融合了enum和const static组合两种方法
template class B { public: enum {value=I*B::value}; static const int value1=I*B::value1; }; template<> class B <1>{ public: enum {value=1}; static const int value1=1; }; int main() { int a[B<5>::value]; int b[B<5>::value1]; int i = B<5>::value; int j = B<5>::value1; cout << i << endl; cout << j << endl; }
这里定义a,b数组,是为了验证B<5>::value和B<5>::value1(value1是采用静态常量的方式,value是枚举类型,两者的区别可以理解为静态常量占用运行期内存空间,而枚举类型不会)编译后是常量(对windows平台有效),当然也可以通过查看汇编代码来发现,下面是我们上面这段代码的关键汇编代码
main: .LFB1442: pushl %ebp .LCFI4: movl %esp, %ebp .LCFI5: subl $984, %esp .LCFI6: andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax subl %eax, %esp movl $120, -972(%ebp) 注释1 movl $120, -976(%ebp) 注释2 subl $8, %esp pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ subl $12, %esp pushl -972(%ebp) pushl $_ZSt4cout .LCFI7:
上述代码中注释1是int i =
B<5>::value;的汇编代码,注释2是int i =
B<5>::value1;得汇编代码,通过上述汇编代码,我们看到编译后,B<5>::value或者B<5>::value1直接就为120,没有经过任何函数调用,作为对比,我们看看第一个例子的汇编代码
main: .LFB1446: pushl %ebp .LCFI4: movl %esp, %ebp .LCFI5: subl $8, %esp .LCFI6: andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax subl %eax, %esp subl $12, %esp leal -5(%ebp), %eax pushl %eax .LCFI7: call _ZN1BILi5EE3numEv 注释1 addl $16, %esp 注释2 movl %eax, -4(%ebp) 注释3 subl $8, %esp pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ subl $12, %esp pushl -4(%ebp)
注释1,注释2,注释3即为int i = B<5>().num();的汇编代码,我们发现它首先进行的是一个函数调用,之后执行一个加操作,把结果放到%eax寄存器中,然后赋值给变量i,正如注释3所示。本来在上述代码中,如果我们这样声明
int a[B<5>().num()];
欲在栈上申请B<5>().num()大小空间,如果B<5>().num()是常量我们可以这样使用,为变量的话我们需要
int *a = new int[B<5>().num()];
而当我编写
int a[B<5>().num()];
时,我在g++ 3.4(Linux)和g++2.9(FreeBSD)下编译居然都没有错误,看来g++编译器对此作了扩展,声明数组的方法,在Unix系统不是正确验证编译常量的方法,最好方式还是看汇编代码。
看来我刚开始要设计的两种方案
方案1
template class S{ public: enum {value=1+S
::value}
};
template<>
class S{
public:
enum {value=1};
};
++>
方案2
template class S{ public: enum {value=1+S<*((const char *)&p+1)>::value} }; template<> class S<'/0'>{ public: enum {value=1}; };
都失败了,在方案1中模板类型不能是const
char*,模板类型只能为类类型,或者类整形类型,比如bool,int,unsigned int,double,short等,方案2中&和*不能用在常量表达式上。
尝试使用常量方式解决编译期间统计字符串长度的方法到此为止了,看来要和类类型结合使用,才有可能解决这个问题,未完待续。