`
61party
  • 浏览: 1055422 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

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

阅读更多

C++ Template Metaprogramming

第三章:深度探索元函数

By David Abraham

(http://www.boost.org/people/dave_abrahams.htm)

By 刘未鹏(pongba)

C++的罗浮宫(http://blog.csdn.net/pongba)

原文链接(http://www.boost-consulting.com/mplbook)

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++》中第一次展示了如何实现它。我们将沿袭他们的思路,只不过重新以元编程的方式来实现。

<chsdate isrocdate="False" islunardate="False" day="30" month="12" year="1899" w:st="on"><strong style="mso-bidi-font-weight: normal"><span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: &quot;Century Gothic&quot;; mso-bidi-font-family: 'Courier New'">3.1.1</span></strong></chsdate> 单位的表示

国际标准单位制规定了物理量的标准单位为:质量(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;

<chsdate isrocdate="False" islunardate="False" day="30" month="12" year="1899" w:st="on"><strong style="mso-bidi-font-weight: normal"><span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: &quot;Century Gothic&quot;; mso-bidi-font-family: 'Courier New'">3.1.2</span></strong></chsdate> 物理量的表示

上面所列的类型仍然是纯粹的元数据。要想对真实的计算进行类型检查,我们还需要以某种方式将它们(元数据)绑定到运行时数据。一个简单的数值外覆类——模板参数为数据类型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(<chmetcnv w:st="on" unitname="F" sourcevalue="1" hasspace="False" negative="False" numbertype="1" tcsc="0">1.0f</chmetcnv>);

quantity<float,mass> m(<chmetcnv w:st="on" unitname="F" sourcevalue="2" hasspace="False" negative="False" numbertype="1" tcsc="0">2.0f</chmetcnv>);

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

m = l; // 编译期错误

<chsdate isrocdate="False" islunardate="False" day="30" month="12" year="1899" w:st="on"><strong style="mso-bidi-font-weight: normal"><span lang="EN" style="FONT-SIZE: 12pt; FONT-FAMILY: &quot;Century Gothic&quot;; mso-ansi-language: EN; mso-bidi-font-family: 'Courier New'">3.1.3</span></strong></chsdate> 实现加法和减法

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

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(<chmetcnv w:st="on" unitname="F" sourcevalue="1" hasspace="False" negative="False" numbertype="1" tcsc="0">1.0f</chmetcnv>);

quantity<float,length> len2(<chmetcnv w:st="on" unitname="F" sourcevalue="2" hasspace="False" negative="False" numbertype="1" tcsc="0">2.0f</chmetcnv>);

len1 = len1 + len2; //ok

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

len1 = len1 = quantity<float, mass>(<chmetcnv w:st="on" unitname="F" sourcevalue="3.7" hasspace="False" negative="False" numbertype="1" tcsc="0">3.7f</chmetcnv>); // error

<chsdate isrocdate="False" islunardate="False" day="30" month="12" year="1899" w:st="on"><strong style="mso-bidi-font-weight: normal"><span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: &quot;Century Gothic&quot;; mso-bidi-font-family: 'Courier New'">3.1.4</span></strong></chsdate> 实现乘法

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

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

意味着结果的单位的指数为相应参数的单位的指数和。商与此类似,为指数差。

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

template <

class Sequence1,

class Sequence2,

class BinaryOperation

border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; margin: 0cm 0cm 0pt; border-left: medium none; padding-top: 0

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics