返回   cpper编程论坛 > Blog > pongba
注册账号 论坛帮助 会员列表 日历事件 搜索 今日新帖 标记版面已读

为这篇文章评分

《C++ Template Metaprogramming》第三章:深度探索元函数

发表于 2006-03-05 03:21 AM 作者: pongba
http://www.boost-consulting.com/mplbook
David Abraham
刘未鹏(pp_liu@msn.com)

C++模板元编程是C++最高阶最抽象也是最强大的能力,也是最前卫的技术,其带来的复用性以及代码强大的表达能力令人瞠目结舌,本章深入讨论C++元编程的机理以及应用,展现C++中最高阶的语义...
——译者


有了前面的基础知识作铺垫,我们来考察模板元编程技术的一个最基本的应用——为传统的不进行类型检查的操作添加静态类型检查。为此,我们将考察一个有关工程科学的实例——几乎在所有涉及科学计算的代码中都可以找到它的应用。在考察该例子的过程中,你将会学到一些重要的新的concepts,并且尝试使用MPL[1]Metaprogramming Library)进行高阶的模板元编程。

3.1 单位[2]分析
物理计算的首要原则是:数值并非是独立的——大多数物理量都有单位。而我们一不小心就会将单位置之脑后,这是件很危险的事情。随着计算变得越来越复杂,维持物理量的正确单位能够避免诸如“将质量赋给长度”和“将加速度和速度相加”之类不经意间就会犯下的错误。这意味着为数值建立一个类型系统。
手动检查类型是件单调而乏味的工作,并且容易导致错误。当人们感到厌烦时,注意力就会分散,从而容易犯错误。然而,类型检查不正是计算机擅长的工作吗?如果我们能够为物理量和单位构建一个C++型别的framework,那么我们从公式中就可以捕获错误,而不用等到它们在现实世界中导致问题的时候。
阻 止单位不同的物理量互操作并不难——我们可以简单地用类来表现单位,并且只允许相同的类(单位)互操作。但是问题远不止这么简单,不同的单位可以通过乘或 除结合起来,从而产生一个复杂的新单位,由于可以不断乘除,所以产生的新单位其复杂度几乎是任意的。看来问题变得更有趣了!例如,牛顿定律(它将力,质 量,加速度三者联系起来):
F=ma
由于质量和加速度有着不同的单位,所以力的单位必须是两者的结合。事实上,加速度的单位就已经是个“混合物”了——单位时间内速度的改变:
dv/dt
又因为速度即“单位时间内经过的距离”,所以加速度的基本单位是:
(l/t)/t=l/t2
并且,加速度通常以“米每平方秒”来衡量。所以,力的单位为:
ml/t2
也就说,力通常以kg(m/s2)或“千克米每平方秒”来衡量。当我们将质量和加速度相乘时,我们除了将数量相乘之外还必须将单位相乘,这可以帮我们确信结果是有意义的。这种(对单位的)簿记的正式名称为单位分析,而我们的下一个任务就是在C++类型系统中实现它。John BartonLee Nackman在它们的著作《Scientific and Engineering C++》中第一次展示了如何实现它。我们将沿袭他们的思路,只不过重新以元编程的方式来实现。

3.1.1单位的表示
国际标准单位制规定了物理量的标准单位为:质量(kg),长度或位置(m),时间(s),电荷(c),温度(oc),密度(kg/m3),角度(o)。为了通用一些,我们的系统必须可以表示七个或七个以上的基本单位,还要能够表示复合单位,比如kg(m/s2))的单位这种经过几个基本单位乘除而成的复合单位。
一般来说,一个复合单位可以看成若干基本单位的幂的乘积[3]。如果要表示这些幂次以便在运行期可以操纵它们,我们可以使用一个数组,其七个元素每个对应一个不同的单位,而其值表示对应单位的幂次:

typedef int dimension[7]; //m l t ...
dimension const mass ={1,0,0,0,0,0,0};
dimension const length ={0,1,0,0,0,0,0};
dimension const time ={0,0,1,0,0,0,0};
...


根据这种表示法,力的表示如下:

dimension const force ={1,1,-2,0,0,0};


也就是说,mlt-2。然而,如果我们想要将单位融入到类型系统[4]中去,这些数组就无法胜任了:它们的类型全都相同,都是dimension!而我们需要的是自身能够表示数值序列的类型,这样质量和长度的类型就是不同的,而两个质量的类型则是相同的。
幸运的是,MPL提供了一组表示类型序列的设施。例如,我们可以构建一个有符号整型的序列:

#include <boost/mpl/vector.hpp>

typedef boost::mpl::vector<
signed char, short, int, long> signed_types;


那么,我们如何用类型序列来表示单位[5]呢?由于数值型的元函数传递和返回的类型是具有内嵌::value的外覆类,所以数值序列其实是外覆类型的序列(另一个多态的例子)。为了使事情变得更为简单,MPL提供了int_<N>类模板,它以一个内嵌的::value来表现它的整型参数N

#include <boost/mpl/int.hpp>

namespace mpl = boost::mpl;[6] // namespace alias
static int const five = mpl::int_<5>::value;


事实上,MPL库包含了一整套整型常量的外覆类,如long_bool_等,每个外覆类对应一个不同类型的整型常量。
现在,我们可以将基本单位构建如下:

typedef mpl::vector<
mpl::int_<1>, mpl::int_<0>, mpl::int_<0>, mpl::int_<0>
, mpl::int_<0>, mpl::int_<0>, mpl::int_<0>
> mass;

typedef mpl::vector<
mpl::int_<0>, mpl::int_<1>, mpl::int_<0>, mpl::int_<0>
, mpl::int_<0>, mpl::int_<0>, mpl::int_<0>
> length;
...


...你很快就会觉得这写起来实在太累人。更糟糕的是,这样的代码难于阅读和验证。代码的本质信息,也就是每个基本单位的幂次,被埋在重复的语法“噪音”中。因此,MPL相应还提供了整型序列外覆类,它允许我们写出类似下面的代码:

#include <boost/mpl/vector_c.hpp>

typedef mpl::vector_c<int,1,0,0,0,0,0,0> mass;
typedef mpl::vector_c<int,0,1,0,0,0,0,0> length; // or position
typedef mpl::vector_c<int,0,0,1,0,0,0,0> time;
typedef mpl::vector_c<int,0,0,0,1,0,0,0> charge;
typedef mpl::vector_c<int,0,0,0,0,1,0,0> temperature;
typedef mpl::vector_c<int,0,0,0,0,0,1,0> intensity;
typedef mpl::vector_c<int,0,0,0,0,0,0,1> angle;


你可以将这个特殊的mpl::vector_c看作与前面那个冗长的mpl::vector一样,尽管它们的类型并不相同。
如果我们愿意,我们还可以定义一些复合单位:

//基本单位:m l t ...
typedef mpl::vector_c<int,0,1,-1,0,0,0,0> velocity; // l/t
typedef mpl::vector_c<int,0,1,-2,0,0,0,0> acceleration;
// l/(t2)
typedef mpl::vector_c<int,1,1,-1,0,0,0,0> momentum; // ml/t
typedef mpl::vector_c<int,1,1,-2,0,0,0,0> force; // ml/(t2)


并且,有时候,标量的单位(如pi,标量的单位即没有单位——译注)也可以这样来描述:

typedef mpl::vector_c<int,0,0,0,0,0,0,0> scalar;


3.1.2物理量的表示
上面所列的类型仍然是纯粹的元数据。要想对真实的计算进行类型检查,我们还需要以某种方式将它们(元数据)绑定到运行时数据。一个简单的数值外覆类——模板参数为数据类型TT的单位——刚好合适:

template <class T, class Dimensions>
struct quantity
{
explicit quantity(T x)
: m_value(x)
{}

T value() const { return m_value; }
private:
T m_value;
};


现在,我们有了将数值和单位联系到一起的办法。例如,我们可以说:

quantity<float,length> l(1.0f);
quantity<float,mass> m(2.0f);


注意到在quantity的类定义体中并没有出现Dimensions模板参数的任何身影,它只在模板参数列表中出现过,其唯一作用是确保lm具有不同的类型。这样,我们就不可能错误地将长度赋给质量:

m = l; //编译期错误


3.1.3实现加法和减法
因为参数的类型(单位)必须总是匹配,所以我们现在可以轻易的写出加法和减法的规则:

template <class T, class D>
quantity<T,D>
operator+(quantity<T,D> x, quantity<T,D> y)
{
return quantity<T,D>(x.value() + y.value());
}

template <class T, class D>
quantity<T,D>
operator-(quantity<T,D> x, quantity<T,D> y)
{
return quantity<T,D>(x.value() - y.value());
}


这样,我们就可以写出类似下面的代码:

quantity<float,length> len1(1.0f);
quantity<float,length> len2(2.0f);
len1 = len1 + len2; //ok


并且,我们不能将不同单位的量相加:

len1 = len1 = quantity<float,mass>(3.7f); //error


3.1.4实现乘法
乘法比加减法复杂一些。到目前为止,运算的参数和结果的单位都是一样的,但是做乘法时,结果的单位往往和两个参数的单位都不相同。对于乘法,下面的式子:

(Xa)(Xb) = X(a+b)


意味着结果的单位的指数为相应参数的单位的指数和。商与此类似,为指数差。
为此,我们使用MPLtransform算法来将两个序列中的对应元素相加。transform是个元函数,它遍历两个并行的输入序列,对于每个位置将两个序列中的对应元素传给一个任意的(用户提供的)二元元函数,并且将结果存入一个输出序列。

template <class Sequence1,
class Sequence2,
class BinaryOperation
>
struct transform; //return a sequence


如果你熟悉STLtransform算法的话,上面的struct transform的形式对于你可能并不陌生,STLtransform算法接受两个运行期的输入序列:

template <
class InputIterator1, class InputIterator2
, class OutputIterator, class BinaryOperation
>
void transform(
评论 0 Email文章
评论总数 0

评论

发表评论 发表评论
作者为 pongba 的最新文章

所有时间均为格林尼治时间 +9。现在的时间是 07:40 PM


Powered by vBulletin® 版本 3.7.0
版权所有 ©2000 - 2008,Jelsoft Enterprises Ltd.
(C) Copy Right All Right Reserved 2001 - 2007

Search Engine Friendly URLs by vBSEO 3.1.0