C++使用Expression Templates

StackOverflow的问答Passing a functor as C++ template parameter的问题很有意思,我以前从来没有想过这样的做法。这个东西专业术语叫:Expression Templates。

Wiki的相关内容由浅入深,解释的很好:

Expression templates are a C++ template metaprogramming technique that builds structures representing a computation at compile time, with structures are evaluated only as needed to produce efficient code for the entire computation.[1]
Expression templates thus allow programmers to bypass the normal order of evaluation of the C++ language and achieve optimizations such as loop fusion.
Expression templates were invented independently by Todd Veldhuizen and David Vandevoorde;[2]:341, [3]
it was Veldhuizen who gave them their name.[2]:341 They are a popular technique for the implementation of linear algebra software.[1]

Motivation and example

Consider a library representing vectors and operations on them. One common mathematical operation is to add two vectors u and v, element-wise, to produce a new vector. The obvious C++ implementation of this operation would be an overloadedoperator+ that returns a new vector object:

class Vec {
    std::vector<double> elems;

  public:
    Vec(size_t n) : elems(n) {}

    double &operator[](size_t i)      { return elems[i]; }
    double operator[](size_t i) const { return elems[i]; }
    size_t size()               const { return elems.size(); }
};

Vec operator+(Vec const &u, Vec const &v) {
    assert(u.size() == v.size());
    Vec sum(u.size());
    for (size_t i = 0; i < u.size(); i++) {
         sum[i] = u[i] + v[i];
    }
    return sum;
}

Users of this class can now write Vec x = a + b; where a and b are both instances of Vec.
A problem with this approach is that more complicated expressions such as Vec x = a + b + c are implemented inefficiently. The implementation first produces a temporary vector to hold a + b, then produces another vector with the elements of cadded in. Even with return value optimization this will allocate memory at least twice and require two loops.
Delayed evaluation solves this problem, and can be implemented in C++ by letting operator+ return an object of a custom type, say VecSum, that represents the unevaluated sum of two vectors, or a vector with a VecSum, etc. Larger expressions then effectively build expression trees that are evaluated only when assigned to an actual Vec variable. But this requires traversing such trees to do the evaluation, which is in itself costly.[4]

Expression templates implement delayed evaluation using expression trees that only exist at compile time. Each assignment to a Vec, such as Vec x = a + b + c, generates a new Vec constructor if needed by template instantiation. This constructor operates on three Vec; it allocates the necessary memory and then performs the computation. Thus only one memory allocation is performed.
An example implementation of expression templates looks like the following. A base class VecExpression represents any vector-valued expression. It is templated on the actual expression type E to be implemented, per the curiously recurring template pattern.

 1 template <typename E>
 2 class VecExpression {
 3   public:
 4     double operator[](size_t i) const { return static_cast<E const&>(*this)[i];     }
 5     size_t size()               const { return static_cast<E const&>(*this).size(); }
 6 
 7     // The following overload conversions to E, the template argument type;
 8     // e.g., for VecExpression<VecSum>, this is a conversion to VecSum.
 9      operator E& () { return static_cast<E&>(*this); }
10      operator const E& () const { return static_cast<const E&>(*this); }
11 };

The Vec class still stores the coordinates of a fully evaluated vector expression, and becomes a subclass of VecExpression.

class Vec : public VecExpression<Vec> {
    std::vector<double> elems;

  public:
    double operator[](size_t i) const { return elems[i]; }
    double &operator[](size_t i)      { return elems[i]; }
    size_t size() const               { return elems.size(); }

    Vec(size_t n) : elems(n) {}

  // construct vector using initializer list 
    Vec(std::initializer_list<double>init){
        for(auto i:init)
            elems.push_back(i);
    }


    // A Vec can be constructed from any VecExpression, forcing its evaluation.
    template <typename E>
    Vec(VecExpression<E> const& vec) : elems(vec.size()) {
        for (size_t i = 0; i != vec.size(); ++i) {
            elems[i] = vec[i];
        }
    }
};

The sum of two vectors is represented by a new type, VecSum, that is templated on the types of the left- and right-hand sides of the sum so that it can be applied to arbitrary pairs of vector expressions. An overloaded operator+ serves as syntactic sugarfor the VecSum constructor.

 1 template <typename E1, typename E2>
 2 class VecSum : public VecExpression<VecSum<E1, E2> > {
 3     E1 const& _u;
 4     E2 const& _v;
 5 
 6 public:
 7     VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
 8         assert(u.size() == v.size());
 9     }
10 
11     double operator[](size_t i) const { return _u[i] + _v[i]; }
12     size_t size()               const { return _v.size(); }
13 };
14   
15 template <typename E1, typename E2>
16 VecSum<E1,E2> const
17 operator+(E1 const& u, E2 const& v) {
18    return VecSum<E1, E2>(u, v);
19 }

With the above definitions, the expression a + b + c is of type
VecSum<VecSum<Vec, Vec>, Vec>

so Vec x = a + b + c invokes the templated Vec constructor with this type as its E template argument. Inside this constructor, the loop body
elems[i] = v[i];

is effectively expanded (following the recursive definitions of operator+ and operator[] on this type) to
elems[i] = a.elems[i] + b.elems[i] + c.elems[i];

with no temporary vectors needed and only one pass through each memory block.
Basic Usage :

 1 int main(){
 2 
 3     Vec v0 = {23.4,12.5,144.56,90.56};
 4     Vec v1 = {67.12,34.8,90.34,89.30};
 5     Vec v2 = {34.90,111.9,45.12,90.5};
 6     VecSum<VecSum<Vec, Vec>, Vec> sum = v0+v1+v2;
 7 
 8     Vec v3(4);
 9 
10     for(int i=0;i<4;++i)
11         v3[i]=sum[i];
12 
13     for(int i=0;i<v3.size();++i)
14         std::cout << v3[i] << std::endl;
15 
16 
17 
18 }

Applications

Expression templates have been found especially useful by the authors of libraries for linear algebra, i.e., for dealing with vectors and matrices of numbers. Among libraries employing expression template are Blaze[5]
, Blitz++[6]
, Boost uBLAS[7]
, Eigen[8]
, POOMA[9]
, Stan Math Library[10]
, and xtensor[11]
.
Outside of vector math, the Spirit parser framework uses expression templates to represent formal grammars and compile these into parsers.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容

  • daoxianzai还zai心疼,后悔自己的大意,漠视和拖拉,本来结果不会这样的,如果…… 那天早上到校后,我发现...
    长跑人阅读 578评论 0 1
  • 表面上我们都长大了,但其实破坏的是当初的美好,最初的那点好东西全给破坏了,如果当初的我们出现在我们面前,一定会笑话...
    森林里的小幺姬阅读 146评论 0 0
  • 在很多人的眼里,深圳是一个新兴的经济特区,是一个移民城市,有一个快节奏生活的超级城市,也有人说深圳特区发展只有短短...
    城市分享阅读 349评论 1 2
  • 80后的孩子深受武侠剧的影响,而作为一名女孩子的我也不例外,生长在乡下,获取的外部信息,就来自小小黑白电视屏幕。课...
    笑忘书风铃草阅读 254评论 0 1