C++模板元编程(一):元编程与运行时编程的对比
by adie
2006-10-21 00:11:23
废话就不多说了,进入正文。
一 函数
1.函数的声明
普通函数: int square(int x, int y);
元函数: template struct Square;
2.函数的定义
运行时函数的典型例子如:
int square(int x, int y)
{
return x * x + y * y;
}
对应的元函数为:
template
struct Squart{
enum{
Value = x * x + y * y
};
};
元函数都是用模板类来定义的,模板参数对应函数参数,在类里面定义常量或类型等来返回结果。通常我们使用 Value 来表示整数结果(通常用 enum 来定义),用 Result(通常用 typedef 来定义) 来表示类型结果。
3.函数的调用
运行时函数调用时的实参可以时常量,也可以时一个变量。而元函数的实参必须时编译期常量。
运行时函数调用例子如:
std::cout<
元函数的调用方法:
std::cout<::Value<
4. 函数的参数
运行时函数的参数不必多说,元函数可以使用的参数有:
(1). 整数类,包括 char, short, int, long,指针等等。可以时有符号的,也可以时无符号的。
(2). typename, 类型,包括基本类型以及自定义类型。
(3). class, 在 C++ 中的定义与上同。但我们使用的时候通常只用于那些只表示自定义类型,不包括基本类型的情况。
(4). 回调元函数,和运行时函数可以接受一个函数指针来进行回调一样,元函数的参数也可以是另外一个元函数,以此来实现元函数的回调。文字本非我所长,还是用例子来说明吧。
先来回顾一下运行时情况是如何使用回调函数的:
两个用于回调的函数如
int add(int x, int y)
{
return x + y;
}
int mul(int x, int y)
{
return x * y;
}
使用回调函数指针的函数
int run(int x, int y, int z, int (*manip)(int, int))
{
return manip(x, manip(y, z));
}
调用方法
run(10, 10, 10, add);
run(10, 10, 10, mul);
好了,现在我们用模板元函数来完成和上面一样的功能:
两个用于回调的函数
template
struct Add{
enum{
Value = x + y
};
};
template
struct Mul{
enum{
Value = x * y
};
};
使用回调函数模板的元函数
template class Manip>
struct Run{
enum{
Value = Manip::Value>::Value
};
};
最后调用的情况
Run<10, 10, 10, Add>::Value
Run<10, 10, 10, Mul>::Value
对比以上两种情况,可以发现他们几乎是相同的。看看函数指针和被称为模板模板参数的元函数指针:
int (*manip)(int, int)
template class Manip
它们也是如此的相似。
PS.以上为了表现普通函数和元函数的共同之处,建立元函数的概念故使用在两者都能使用的整数作为例子。在实际应用中元函数操作的通常是类型,而不是编译期整数。另外对其差别也未曾细述,不过只要牢记元函数只由编译器来解释执行,不会在运行期留下任何痕迹,不能使用运行期的任何东西,其差别就不难把握。
二 结构化
结构化程序所要求的 3 大结构:顺序、选择、循环在元编程中都能够实现,但其使用起来就不象元函数那么漂亮了,下面依次说明。
1. 顺序结构
顺序结构是最简单的结构,其实现也很简单,举例就看明白了:
template
struct Max{
private:
enum{
temp = x > y ? x : y
};
public:
enum{
Value = temp > z ? temp : z
};
};
唯一需要注意下的就是最好能够吧中间的计算过程放在 private 区,只把要返回的内容放在 public 区。
2. 选择结构
元编程里的语句有限(基本上就是 enum 和 typedef, 有时也可以使用类的静态变量或静态函数等等),没有运行时的 if-else, switch-case 语句。在元编程里实现选择结构时利用 C++ 提供的模板偏特化(这里把全特化视为一种特殊的偏特化,不另外指明了)机制来模拟的。下面我们以实现取绝对值的函数来说明。当然这个例子其实可以用 x < 0 ? -x : x 一个表达式来完成的,但对于需要操作类型的元函数来说是无法使用 ? : 表达式的,本例旨在说明选择结构。
先来看按普通选择结构的实现:
int abs(int x)
{
if(x < 0)
return -x;
else
return x;
}
下面是元编程使用模板偏特化的方法实现:
template struct AbsImp;
template
struct AbsImp{
enum{
Value = -x
};
};
template
struct AbsImp{
enum{
Value = x
};
};
template
struct Abs{
enum{
Value = AbsImp::Value
};
};
这个样子确实不怎么漂亮啊,不过我们可以写一个模板来简化:
template struct if_else;
template
struct if_else{
enum{
Value = ifv
};
};
template
struct if_else{
enum{
Value = elsev
};
};
有了以上这个模板我们的 Abs 函数可以简化为:
template
struct Abs{
enum{
Value = if_else::Value
};
};
上面的 if_else 模板看上去和 ? : 表达式一样,关键在于稍加修改就可以让它用于操作类型。 if_else 模板是可以通用的,只需要写一次,而且更棒的是,Loki, boost::mpl 等库中已经为我们提供了类似的 if, switch 模板函数了。
2. 循环结构
元编程中也没有直接的循环结构语句,要实现循环结构是靠递归和选择结构来模拟的。我们知道,任何递归都可以用循环来代替的。其实,任何循环也可以用递归来代替,前提是你有足够的堆栈来支持足够的递归曾数。编译器支持的元函数递归层数是有限制的,所以模拟出来的循环结构的循环次数也是有限制的。
先来看看运行期函数如何用递归和选择来实现循环,例如要实现如下的循环:
int f()
{
int num = 0;
for(int i = 0; i < 10; ++i)
{
num = num + i;
}
return num;
}
如果用递归来模拟的话:
int recuf(int num, int i)
{
if(i == 10)
return num;
else
return recuf(num, i+1) + i;
}
int f()
{
return recuf(0, 0);
}
根据上面的递归方法我们可以对比元编程中的方法:
template struct metaF;
template
struct metaF{
enum{
Value = num
};
}; // if(i == 10) return num
template
struct metaF{
enum{
Value = metaF::Value + i
};
}; // else return refcuf(num, i+1) + i
利用偏特化模拟了 if - else, 在 Value = metaF::Value + i 中使用递归,最后完成了和循环一样的功能。
三 库
有了函数,有了顺序、选择、循环结构,我们可以做很多事情了。不过和传统编程一样,我们还需要库来提供一些基本功能,避免重复造车轮的工作。标准模板库中的某些类或许对元编程有些用处,但他们不是为元编程设计的,功能远远不够。目前元编程方面的库还远不能和传统编程的库相比,似乎只有 boost::mpl 及一些相关组件可供使用。
▲评论