初探模板元编程

关于模板元编程的知识也有所了解,相关的书籍也看过几本,但是至今还没有亲手写过一个模板元程序,原因就是没有一个合适的机会应用模板元编程技术,今天在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中&和*不能用在常量表达式上。

尝试使用常量方式解决编译期间统计字符串长度的方法到此为止了,看来要和类类型结合使用,才有可能解决这个问题,未完待续。

时间: 2025-01-24 06:08:48

初探模板元编程的相关文章

利用模板元编程实现解循环优化

简介 在<C++ Templates: The Complete Guide>一书中(以下简称书),提出了模板元编程最早的实际应用之一:在数值运算中进行解循环优化. 而本文的标题是噱头!本文的真正目的是指出这种优化措施在增加复杂性的同时,并不一定能明显改善效率.应当谨慎使用该技术--默认不使用该技术,在点积计算确实是效率瓶颈时考虑采用该技术,并认真测试该技术是否真能提高效率. 背景 数值运算库中常常需要提供向量点积(dot_product)运算.其定义用C++代码描述也许更清楚- templa

C++箴言:谨慎使用模板元编程

template metaprogramming (TMP)(模板元编程)是写 template-based(基于模板)的运行于编译期间的 C++ 程序的过程.考虑一下:一个 template metaprogram(模板元程序)是用 C++ 写的运行于 C++ 编译器中的程序.当一个 TMP 程序运行完成,它的输出--从 templates(模板)实例化出的 C++ 源代码片断--随后被正常编译. 如果你仅把它看作古怪的特性而没有打动你,那你就不会对它有足够的深入的思考. C++ 并不是为 t

Functional Programming与C++的模板元编程

先来看一个例子: #include <stdio.h> template <int depth> class Fibnacci { public: static const int value = Fibnacci<depth-1>::value + Fibnacci<depth-2>::value; }; template <> class Fibnacci<0> { public: static const int value =

【C/C++学院】0816-引用包装器/仿函数/转义字符 R”()”/using别名/模板元编程 比递归优化/智能指针/多线程/静态断言以及调试技能的要求 assert

引用包装器  std::ref(变量) #include<iostream> template<class T> void com(T arg)//模板函数,引用无效,引用包装器 { std::cout <<"com ="<< &arg << "\n"; arg++; } void main() { int count = 10; int & rcount = count; com(coun

C++之:模板元编程(一)

一.概念 利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行. 模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数.返回值取得任意类型. 模板是一种对类型进行参数化的工具: 通常有两种形式:函数模板和类模板: 函数模板针对仅参数类型不同的函数: 类模板针对仅数据成员和成员函数类型不同的类. 使用模板的目的就是能够让程序员编写与类型无关的代码.比如编写了一个交换两个整型

C++之:模板元编程(三) 默认模板参数

一.类模板的默认模板参数原则 1.可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值.函数模板和类模板都可以为模板的非类型形参提供默认值. 2.类模板的类型形参默认值形式为: template<class T1, class T2=int> class A{}; 为第二个模板类型形参T2提供int型的默认值. 3.类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如 template<class

使用模板元编程快速的得到斐波那契数。。

这是一种将运行时消耗转移到编译器消耗的方法,是c++模板的一种应用. 当你的程序运行时效率需要特别高的时候,可以考虑这样的方法. 模板实例化的时候需要常量: #include <iostream> using namespace std; template < unsigned N > struct Fib { enum { Val = Fib<N-1>::Val + Fib<N-2>::Val //递归.. }; }; template<> /

C++之:模板元编程(二) 模板形参

一.模板形参概述 有三种类型的模板形参:类型形参,非类型形参和模板形参. 二.类型形参 2.1 .类型模板形参 类型形参由关见字class或typename后接说明符构成,如template<class T> void h(T a){};其中T就是一个类型形参,类型形参的名字由用户自已确定.模板形参表示的是一个未知的类型.模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同,即可以用于指定返回类型,变量声明等. 2.2. 不能为同一个模板类型形参指

引用内部函数绑定机制,R转义字符,C++引用,别名,模板元,宏,断言,C++多线程,C++智能指针

 1.引用内部函数绑定机制 #include<iostream> #include<functional>   usingnamespacestd; usingnamespacestd::placeholders;   //仿函数,创建一个函数指针,引用一个结构体内部或者一个类内部的共有函数 structMyStruct {    voidadd(inta)    {        cout <<a <<endl;    }    voidadd2(in