表达式模板和C ++ 11 [英] Expression templates and C++11

查看:93
本文介绍了表达式模板和C ++ 11的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们看一下表达式模板的一个特殊好处:ET可用于避免内存中的矢量大小的临时变量,这些临时变量会在重载运算符中发生,例如:

Let's look at one particular benefit of expression templates: ETs can be used to avoid vector-sized temporaries in memory which occur in overloaded operators like:

template<typename T>
std::vector<T> operator+(const std::vector<T>& a, const std::vector<T>& b)
{
  std::vector<T> tmp;   // vector-sized temporary
  for_each(...);
  return tmp;
}

在C ++ 11中,此函数的return语句应用移动语义。没有向量的副本。

In C++11 the return statement of this function applies move semantics. No copy of the vector. That's a win.

但是,如果我看一个简单的表达式,例如

However, if I look at a simple expression like

d = a + b + c;

我看到上面的函数被调用了两次(对于 operator + ),而最终赋值可以通过移动语义完成。

I see that the above function gets called twice (for both operator+) while the final assignment can be done with move semantics.

总共执行了2个循环。就是说我放了一个临时文件,然后马上读回来。对于大向量,这不在高速缓存中。这比表达模板还差。他们可以在1个循环中完成全部操作。 ET可以执行等同于以下代码的

In total 2 loops are executed. Means that I put out a temporary and read it back in right after. For big vectors this falls out of cache. That's worse than expression templates. They can do the whole thing in just 1 loop. ETs can execute the above code equivalent to:

for(int i=0 ; i < vec_length ; ++i)
  d[i] = a[i] + b[i] + c[i];

我想知道lambda与移动语义或任何其他新功能是否能像ET一样好。有什么想法吗?

I was wondering whether lambdas together with move semantics or any other new feature can do as good as ETs. Any thoughts?

编辑:

基本上,使用ET技术编译器用
类型系统构建一个类似于代数表达式的解析树
。该树由内部节点和叶节点组成。
内部节点表示操作(加法,乘法等),
叶子节点表示对数据对象的引用。

Basically, using the ET technique the compiler builds a parse tree that resembles the algebraic expression with it's type system. This tree consists of inner nodes and leaf nodes. The inner nodes represent operations (addition, multiplication, etc.) and the leaf nodes represent references to the data objects.

我试图考虑一下
堆栈机的整个计算过程:从操作堆栈中进行操作,然后从参数堆栈中拉出
下一个参数,并评估该操作。
将结果放回堆栈中等待操作。

I tried to think of the whole computation process in the fashion of a stack machine: Take an operation from an operation stack and pull the next arguments from the argument stack and evaluate the operation. Put the result back on the stack waiting for the operation.

代表这两个不同的对象(操作堆栈和数据
叶子堆栈)将用于操作的 std :: tuple 和用于数据叶的
<$ c $ std :: tuple 捆绑在一起放入 std :: pair<> 中。最初,我
使用了 std:vector ,但这会导致运行时开销。

To represent these two different objects (operation stack and data leaf stack) I bundled together a std::tuple for the operations and a std::tuple for the data leaves into a std::pair<>. Initially I used a std:vector but that resulted in runtime overhead.

整个过程都在进行分两个阶段:堆栈机初始化
,其中操作和参数堆栈被初始化。然后,通过将配对的容器
分配给向量来触发
评估阶段。

The whole process goes in two phases: Stack machine initialisation where the operation and argument stack are initialised. And the evaluation phase which is triggered by assigning the paired containers to the vector.

我创建了一个类 Vec 拥有一个私有的 array< int,5>
有效载荷),并具有一个重载的赋值运算符

I created a class Vec which holds a private array<int,5> (the payload) and which features an overloaded assignment operator that takes the "expression".

全局运算符* 对于使用
Vec 和表达式以在
情况下也能正确处理,而我们的
不仅仅是 a * b 。 (注意,我将这个
的教育示例切换为乘法-基本上是为了在组装器中快速发现
imull 。)

The global operator* is overloaded for all combinations of taking Vec and "expression" to enable the correct handling also in the case where we have more than just a*b. (Notice, I switched for this educational example to the multiplication - basically to quickly spot the imull in the assembler.)

在评估开始之前首先要做的是从涉及的 Vec 对象中提取
值并初始化参数
堆栈。这对于避免在
周围放置不同种类的对象是很有必要的:可索引向量和不可索引结果。这就是
Extractor 的用途。再好一点:使用可变参数模板,在这种情况下
不会导致运行时开销(所有操作都在
编译时完成)。

What is done first before the evaluation starts is "extracting" the values out of the involved Vec objects and initializing the argument stack. That was necessary to not have different kinds of objects lying around: Indexable vectors and non-indexable results. This is what the Extractor is for. Nice thing again: Variadic templates are used which in this case results in no run-time overhead (all this is done at compile time).

整个事情都起作用。表达式得到了很好的评估(我也
添加了添加项,但此处不适合代码)。在
以下,您可以看到汇编程序输出。只是原始计算,完全符合您
想要的那样:使用ET技术进行比较。

The whole thing works. The expression is nicely evaluated (I also added the addition, but that is left out here to fit the code). Below you can see the assembler output. Just raw compuation, exactly as you want it to be: En-par with ET technique.

结果。 C ++ 11的新语言功能提供了可变的
模板,这些模板(以及模板元编程)打开了编译时间计算的
领域。我在这里展示了如何使用
可变参数模板的好处来产生与
传统ET技术一样好的代码。

Upshot. The new language features of C++11 offer the variadic templates which (along with template meta-programming) open up the area of compile time computation. I showed here how the benefits of variadic templates can be used to produce code as good as with the traditional ET technique.

#include<algorithm>
#include<iostream>
#include<vector>
#include<tuple>
#include<utility>
#include<array>



template<typename Target,typename Tuple, int N, bool end>
struct Extractor {
  template < typename ... Args >
  static Target index(int i,const Tuple& t, Args && ... args)
  {
    return Extractor<Target, Tuple,  N+1, 
             std::tuple_size<Tuple>::value == N+1>::
      index(i, t , std::forward<Args>(args)..., std::get<N>(t).vec[i] );
  }
};

template < typename Target, typename Tuple, int N >
struct Extractor<Target,Tuple,N,true>
{
    template < typename ... Args >
    static Target index(int i,Tuple const& t, 
            Args && ... args) { 
      return Target(std::forward<Args>(args)...); }
};

template < typename ... Vs > 
std::tuple<typename std::remove_reference<Vs>::type::type_t...>
extract(int i , const std::tuple<Vs...>& tpl)
{
  return Extractor<std::tuple<typename std::remove_reference<Vs>::type::type_t...>,
           std::tuple<Vs...>, 0,
           std::tuple_size<std::tuple<Vs...> >::value == 0>::index(i,tpl);
}


struct Vec {
  std::array<int,5> vec;
  typedef int type_t;

  template<typename... OPs,typename... VALs>
  Vec& operator=(const std::pair< std::tuple<VALs...> , std::tuple<OPs...> >& e) {
    for( int i = 0 ; i < vec.size() ; ++i ) {
      vec[i] = eval( extract(i,e.first) , e.second );
    }
  }
};




template<int OpPos,int ValPos, bool end>
struct StackMachine {
  template<typename... OPs,typename... VALs>
  static void eval_pos( std::tuple<VALs...>& vals , const std::tuple<OPs...> & ops )
  {
    std::get<ValPos+1>( vals ) =
      std::get<OpPos>(ops).apply( std::get<ValPos>( vals ) , 
                  std::get<ValPos+1>( vals ) );
    StackMachine<OpPos+1,ValPos+1,sizeof...(OPs) == OpPos+1>::eval_pos(vals,ops);
  }
};

template<int OpPos,int ValPos>
struct StackMachine<OpPos,ValPos,true> {
  template<typename... OPs,typename... VALs>
  static void eval_pos( std::tuple<VALs...>& vals , 
            const std::tuple<OPs...> & ops )
  {}
};



template<typename... OPs,typename... VALs>
int eval( const std::tuple<VALs...>& vals , const std::tuple<OPs...> & ops )
{
  StackMachine<0,0,false>::eval_pos(const_cast<std::tuple<VALs...>&>(vals),ops);
  return std::get<sizeof...(OPs)>(vals);
}




struct OpMul {
  static int apply(const int& lhs,const int& rhs)  {
    return lhs*rhs;
  }
};

std::pair< std::tuple< const Vec&, const Vec& > , std::tuple<OpMul> >
operator*(const Vec& lhs,const Vec& rhs)
{
  return std::make_pair( std::tuple< const Vec&, const Vec& >( lhs , rhs ) , 
             std::tuple<OpMul>( OpMul() ) );
}

template<typename... OPs,typename... VALs>
std::pair< std::tuple< const Vec&, VALs... > , std::tuple<OPs...,OpMul> >
operator*(const Vec& lhs,const std::pair< std::tuple< VALs... > , std::tuple<OPs...> >& rhs)
{
  return std::make_pair( std::tuple_cat( rhs.first , std::tuple< const Vec& >(lhs)  ) , 
             std::tuple_cat( rhs.second , std::tuple<OpMul>( OpMul() )  ) );
}

template<typename... OPs,typename... VALs>
std::pair< std::tuple< const Vec&, VALs... > , std::tuple<OPs...,OpMul> >
operator*(const std::pair< std::tuple< VALs... > , std::tuple<OPs...> >& lhs,
      const Vec& rhs)
{
  return std::make_pair( std::tuple_cat( lhs.first , std::tuple< const Vec& >(rhs)  ) , 
             std::tuple_cat( lhs.second , std::tuple<OpMul>( OpMul() ) ) );
}

int main()
{
  Vec d,c,b,a;


  for( int i = 0 ; i < d.vec.size() ; ++i ) {
    a.vec[i] = 10+i;
    b.vec[i] = 20+i;
    c.vec[i] = 30+i;
    d.vec[i] = 0;
  }

  d = a * b * c * a;

  for( int i = 0 ; i < d.vec.size() ; ++i ) 
    std::cout << d.vec[i] << std::endl;
}

使用 g ++-4.6 -O3生成的汇编器(我必须在向量初始化中加入一些运行时相关性,以使编译器在编译时不会计算整个事情,而您实际上会看到 imull

Assembler generated with g++-4.6 -O3 (I had to put some runtime dependence into the vector initialization so that the compiler doesn't calculate the whole thing at compile time and you actually see the imull instaructions.)

imull   %esi, %edx
imull   32(%rsp), %edx
imull   %edx, %esi
movl    68(%rsp), %edx
imull   %ecx, %edx
movl    %esi, (%rsp)
imull   36(%rsp), %edx
imull   %ecx, %edx
movl    104(%rsp), %ecx
movl    %edx, 4(%rsp)
movl    72(%rsp), %edx
imull   %ecx, %edx
imull   40(%rsp), %edx
imull   %ecx, %edx
movl    108(%rsp), %ecx
movl    %edx, 8(%rsp)
movl    76(%rsp), %edx
imull   %ecx, %edx
imull   44(%rsp), %edx
imull   %ecx, %edx
movl    112(%rsp), %ecx
movl    %edx, 12(%rsp)
movl    80(%rsp), %edx
imull   %ecx, %edx
imull   %eax, %edx
imull   %ecx, %edx
movl    %edx, 16(%rsp)


推荐答案


我想知道lambda与移动语义或任何其他新功能是否能像ET一样好。有什么想法吗?

I was wondering whether lambdas together with move semantics or any other new feature can do as good as ETs. Any thoughts?

快速解答

移动语义本身并不是万灵药,在C ++ 11中仍需要诸如表达模板(ET)之类的技术来消除诸如移动数据之类的开销!因此,在深入研究其余答案,移动语义等内容之前快速回答您的问题并不能完全取代ET,如下所示。

Move semantics are not a total panacea on their own --techniques such as expression templates (ETs) are still needed in C++11 to eliminate overheads such as moving data around! So, to answer your question quickly before diving into the rest of my answer, move semantics, etc. doesn't completely replace ETs as my answer illustrates below.

详细的答案

ET通常会返回代理对象以将评估推迟到以后,因此C ++ 11语言功能在获得代码之前没有立即明显的好处。触发计算。就是说,不希望编写ET代码,但是会在使用代理构建表达式树时触发运行时代码生成。不错,C ++ 11的移动语义和完美的转发可以帮助避免这种开销,否则这些开销就会发生。 (在C ++ 03中是不可能做到的。)

ETs typically return proxy objects to defer evaluation until later, so there is no immediate apparent benefit of C++11 language features until the code that triggers the computation. That said, one would not want to write ET code, however, that triggers run-time code generation during the building of the expression tree with the proxies. Nicely, C++11's move semantics and perfect forwarding can help avoid such overheads should that otherwise occur. (Such would not have been possible in C++03.)

本质上,在编写ET时,人们希望利用语言功能以一种方式来生成最佳代码。所涉及的代理对象的成员函数被调用。在C ++ 11中,这将包括使用完美的转发,在复制上移动语义等,如果实际上仍然需要编译器已经可以做的事情。游戏的名称是最小化生成的运行时代码和/或最大化运行时速度和/或最小化运行时开销。

Essentially, when writing ETs one wants to exploit the language features in a way to generate optimal code once the member function(s) of the involved proxy objects are invoked. In C++11 this will include using perfect forwarding, move semantics over copying, etc. if such is actually still needed over and above what the compiler can already do. The name of the game is to minimize the run-time code generated and/or maximize the run-time speed and/or minimize the run-time overhead.

我想要实际尝试一些具有C ++ 11功能的ET,看看是否可以使用 a = b + c + d; 表达式消除所有中间临时实例类型。 (因为这只是我平时工作的一个有趣的休息,所以我没有将它与仅使用C ++ 03进行比较或编写ET代码。而且我也不担心下面出现的代码完善的所有方面。)

I wanted to actually try some ETs with C++11 features to see if I could elide ALL intermediate temporary instance types with a a = b + c + d; expression. (As this was just a fun break from my normal activities so I did not compare it to or write ET code purely using C++03. Also I did not worry about all aspects of code polishing that appears below.)

首先,我没有使用lambda,因为我更喜欢使用显式类型和函数,因此我不会就您的问题主张/反对lambda。我的猜测是,它们将类似于使用函子,并且性能不比下面的非ET代码好(即需要移动)-至少直到编译器可以为此使用自己的内部ET自动优化lambda为止。但是,我编写的代码利用了移动语义和完美的转发。这是我从结果开始,然后最终呈现代码的步骤。

To start with, I did not use lambdas --as I preferred to use explicit types and functions-- so I won't argue for/against lambdas with respect to your question. My guess is that they would be similar to using functors and performing no better than the non-ET code below (i.e., moves would be required) --at least until compilers can automatically optimize lambdas using their own internal ETs for such. The code I wrote, however, exploits move semantics and perfect forwarding. Here's what I did starting with the results and then finally presenting the code.

我创建了 math_vector< N> 类,其中 N == 3 ,它定义了 std :: array< long double,N> 的内部私有实例。成员是默认构造函数,复制和移动构造函数和赋值,初始化列表构造函数,析构函数,swap()成员,用于访问向量元素的运算符[]和运算符+ =。此代码不带任何表达式模板使用:

I created a math_vector<N> class where N==3 and it defines an internal private instance of std::array<long double, N>. The members are a default constructor, copy and move constructors and assignments, an initializer list constructor, a destructor, a swap() member, operator [] to access elements of the vector and operator +=. Used without any expression templates, this code:

{
  cout << "CASE 1:\n";
  math_vector<3> a{1.0, 1.1, 1.2};
  math_vector<3> b{2.0, 2.1, 2.2};
  math_vector<3> c{3.0, 3.1, 3.2};
  math_vector<3> d{4.0, 4.1, 4.2};
  math_vector<3> result = a + b + c + d;
  cout << '[' << &result << "]: " << result << "\n";
}

输出(使用 clang ++ 3.1或 g ++ 4.8,带有- std = c ++ 11 -O3 ):

outputs (when compiled with clang++ 3.1 or g++ 4.8 with -std=c++11 -O3):

CASE 1:
0x7fff8d6edf50: math_vector(initlist)
0x7fff8d6edef0: math_vector(initlist)
0x7fff8d6ede90: math_vector(initlist)
0x7fff8d6ede30: math_vector(initlist)
0x7fff8d6edd70: math_vector(copy: 0x7fff8d6edf50)
0x7fff8d6edda0: math_vector(move: 0x7fff8d6edd70)
0x7fff8d6eddd0: math_vector(move: 0x7fff8d6edda0)
0x7fff8d6edda0: ~math_vector()
0x7fff8d6edd70: ~math_vector()
[0x7fff8d6eddd0]: (10,10.4,10.8)
0x7fff8d6eddd0: ~math_vector()
0x7fff8d6ede30: ~math_vector()
0x7fff8d6ede90: ~math_vector()
0x7fff8d6edef0: ~math_vector()
0x7fff8d6edf50: ~math_vector()

,即使用初始化程序列表(即 initlist 项)的四个显式构造的实例,日e result 变量(即 0x7fff8d6eddd0 ),并另外复制和移动三个对象。

i.e., the four explicit constructed instances using initializer lists (i.e., the initlist items), the result variable (i.e., 0x7fff8d6eddd0), and, also makes an additional three objects copying and moving.

为了只关注临时工和搬家,我创建了第二种情况,只创建结果作为命名变量-所有其他是右值:

To only focus on temporaries and moving, I created a second case that only creates result as a named variable --all others are rvalues:

{
  cout << "CASE 2:\n";
  math_vector<3> result =
    math_vector<3>{1.0, 1.1, 1.2} +
    math_vector<3>{2.0, 2.1, 2.2} +
    math_vector<3>{3.0, 3.1, 3.2} +
    math_vector<3>{4.0, 4.1, 4.2}
  ;
  cout << '[' << &result << "]: " << result << "\n";
}

输出(再次使用ET时):

which outputs this (again when ETs are NOT used):

CASE 2:
0x7fff8d6edcb0: math_vector(initlist)
0x7fff8d6edc50: math_vector(initlist)
0x7fff8d6edce0: math_vector(move: 0x7fff8d6edcb0)
0x7fff8d6edbf0: math_vector(initlist)
0x7fff8d6edd10: math_vector(move: 0x7fff8d6edce0)
0x7fff8d6edb90: math_vector(initlist)
0x7fff8d6edd40: math_vector(move: 0x7fff8d6edd10)
0x7fff8d6edb90: ~math_vector()
0x7fff8d6edd10: ~math_vector()
0x7fff8d6edbf0: ~math_vector()
0x7fff8d6edce0: ~math_vector()
0x7fff8d6edc50: ~math_vector()
0x7fff8d6edcb0: ~math_vector()
[0x7fff8d6edd40]: (10,10.4,10.8)
0x7fff8d6edd40: ~math_vector()

这更好:只创建额外的移动对象。

which is better: only extra move objects are created.

但是我想要更好:我想要零个临时工,并拥有代码,就像我用th对其进行硬编码一样一个常规的编码警告:仍将创建所有显式实例化的类型(即,四个 initlist 构造函数和 result ) 。为此,我然后添加了表达式模板代码,如下所示:

But I wanted better: I wanted zero extra temporaries and to have the code as if I hard-coded it with the one normal coding caveat: all explicitly instantiated types would still be created (i.e., the four initlist constructors and result). To accomplish this I then added expression template code as follows:


  1. 代理 math_vector_expr< LeftExpr,BinaryOp,RightExpr> 类被创建来保存尚未计算的表达式,

  2. 代理 plus_op 类被创建来保存添加操作

  3. 将构造函数添加到 math_vector 以接受 math_vector_expr 对象,以及

  4. 启动器成员函数已添加,以触发表达式模板的创建。

  1. a proxy math_vector_expr<LeftExpr,BinaryOp,RightExpr> class was created to hold an expression not computed yet,
  2. a proxy plus_op class was created to hold the addition operation,
  3. a constructor was added to math_vector to accept a math_vector_expr object, and,
  4. "starter" member functions were added to trigger the creation of the expression template.

使用ET的结果非常好:无论哪种情况,都不会临时占用!上面的前两种情况现在输出:

The results using ETs are wonderful: no extra temporaries in either case! The previous two cases above now output:

CASE 1:
0x7fffe7180c60: math_vector(initlist)
0x7fffe7180c90: math_vector(initlist)
0x7fffe7180cc0: math_vector(initlist)
0x7fffe7180cf0: math_vector(initlist)
0x7fffe7180d20: math_vector(expr: 0x7fffe7180d90)
[0x7fffe7180d20]: (10,10.4,10.8)
0x7fffe7180d20: ~math_vector()
0x7fffe7180cf0: ~math_vector()
0x7fffe7180cc0: ~math_vector()
0x7fffe7180c90: ~math_vector()
0x7fffe7180c60: ~math_vector()

CASE 2:
0x7fffe7180dd0: math_vector(initlist)
0x7fffe7180e20: math_vector(initlist)
0x7fffe7180e70: math_vector(initlist)
0x7fffe7180eb0: math_vector(initlist)
0x7fffe7180d20: math_vector(expr: 0x7fffe7180dc0)
0x7fffe7180eb0: ~math_vector()
0x7fffe7180e70: ~math_vector()
0x7fffe7180e20: ~math_vector()
0x7fffe7180dd0: ~math_vector()
[0x7fffe7180d20]: (10,10.4,10.8)
0x7fffe7180d20: ~math_vector()

即分别是5个构造函数调用和5个析构函数调用。实际上,如果您要求编译器在4个 initlist 构造函数调用与 result 输出之间生成汇编代码。一个得到了漂亮的汇编代码字符串:

i.e., exactly 5 constructor calls and 5 destructor calls in each case. In fact, if you ask the compiler to generate the assembler code between the 4 initlist constructor calls and the outputting of result one gets this beautiful string of assembler code:

fldt    128(%rsp)
leaq    128(%rsp), %rdi
leaq    80(%rsp), %rbp
fldt    176(%rsp)
faddp   %st, %st(1)
fldt    224(%rsp)
faddp   %st, %st(1)
fldt    272(%rsp)
faddp   %st, %st(1)
fstpt   80(%rsp)
fldt    144(%rsp)
fldt    192(%rsp)
faddp   %st, %st(1)
fldt    240(%rsp)
faddp   %st, %st(1)
fldt    288(%rsp)
faddp   %st, %st(1)
fstpt   96(%rsp)
fldt    160(%rsp)
fldt    208(%rsp)
faddp   %st, %st(1)
fldt    256(%rsp)
faddp   %st, %st(1)
fldt    304(%rsp)
faddp   %st, %st(1)
fstpt   112(%rsp)

使用 g ++ clang ++ 输出类似(甚至更小的)代码。没有函数调用,等等。-只是一堆添加,正是人们想要的!

with g++ and clang++ outputs similar (even smaller) code. No function calls, etc. --just a bunch of adds which is EXACTLY what one wants!

实现此目标的C ++ 11代码如下。只需 #define DONT_USE_EXPR_TEMPL 不使用ET或根本不定义它以使用ET。

The C++11 code to achieve this follows. Simply #define DONT_USE_EXPR_TEMPL to not use ETs or don't define it at all to use ETs.

#include <array>
#include <algorithm>
#include <initializer_list>
#include <type_traits>
#include <iostream>

//#define DONT_USE_EXPR_TEMPL

//===========================================================================

template <std::size_t N> class math_vector;

template <
  typename LeftExpr,
  typename BinaryOp,
  typename RightExpr
>
class math_vector_expr
{
  public:
    math_vector_expr() = delete;

    math_vector_expr(LeftExpr l, RightExpr r) : 
      l_(std::forward<LeftExpr>(l)), 
      r_(std::forward<RightExpr>(r))
    {
    }

    // Prohibit copying...
    math_vector_expr(math_vector_expr const&) = delete;
    math_vector_expr& operator =(math_vector_expr const&) = delete;

    // Allow moves...
    math_vector_expr(math_vector_expr&&) = default;
    math_vector_expr& operator =(math_vector_expr&&) = default;

    template <typename RE>
    auto operator +(RE&& re) const ->
      math_vector_expr<
        math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&,
        BinaryOp,
        decltype(std::forward<RE>(re))
      >
    {
      return 
        math_vector_expr<
          math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&,
          BinaryOp,
          decltype(std::forward<RE>(re))
        >(*this, std::forward<RE>(re))
      ;
    }

    auto le() -> 
      typename std::add_lvalue_reference<LeftExpr>::type
      { return l_; }

    auto le() const ->
      typename std::add_lvalue_reference<
        typename std::add_const<LeftExpr>::type
      >::type
      { return l_; }

    auto re() -> 
      typename std::add_lvalue_reference<RightExpr>::type
      { return r_; }

    auto re() const -> 
      typename std::add_lvalue_reference<
        typename std::add_const<RightExpr>::type
      >::type
      { return r_; }

    auto operator [](std::size_t index) const ->
      decltype(
        BinaryOp::apply(this->le()[index], this->re()[index])
      )
    {
      return BinaryOp::apply(le()[index], re()[index]);
    }

  private:
    LeftExpr l_;
    RightExpr r_;
};

//===========================================================================

template <typename T>
struct plus_op
{
  static T apply(T const& a, T const& b)
  {
    return a + b;
  }

  static T apply(T&& a, T const& b)
  {
    a += b;
    return std::move(a);
  }

  static T apply(T const& a, T&& b)
  {
    b += a;
    return std::move(b);
  }

  static T apply(T&& a, T&& b)
  {
    a += b;
    return std::move(a);
  }
};

//===========================================================================

template <std::size_t N>
class math_vector
{
  using impl_type = std::array<long double, N>;

  public:
    math_vector()
    {
      using namespace std;
      fill(begin(v_), end(v_), impl_type{});
      std::cout << this << ": math_vector()" << endl;
    }

    math_vector(math_vector const& mv) noexcept
    {
      using namespace std;
      copy(begin(mv.v_), end(mv.v_), begin(v_));
      std::cout << this << ": math_vector(copy: " << &mv << ")" << endl;
    }

    math_vector(math_vector&& mv) noexcept
    {
      using namespace std;
      move(begin(mv.v_), end(mv.v_), begin(v_));
      std::cout << this << ": math_vector(move: " << &mv << ")" << endl;
    }

    math_vector(std::initializer_list<typename impl_type::value_type> l)
    {
      using namespace std;
      copy(begin(l), end(l), begin(v_));
      std::cout << this << ": math_vector(initlist)" << endl;
    }

    math_vector& operator =(math_vector const& mv) noexcept
    {
      using namespace std;
      copy(begin(mv.v_), end(mv.v_), begin(v_));
      std::cout << this << ": math_vector op =(copy: " << &mv << ")" << endl;
      return *this;
    }

    math_vector& operator =(math_vector&& mv) noexcept
    {
      using namespace std;
      move(begin(mv.v_), end(mv.v_), begin(v_));
      std::cout << this << ": math_vector op =(move: " << &mv << ")" << endl;
      return *this;
    }

    ~math_vector()
    {
      using namespace std;
      std::cout << this << ": ~math_vector()" << endl;
    }

    void swap(math_vector& mv)
    {
      using namespace std;
      for (std::size_t i = 0; i<N; ++i)
        swap(v_[i], mv[i]);
    }

    auto operator [](std::size_t index) const
      -> typename impl_type::value_type const&
    {
      return v_[index];
    }

    auto operator [](std::size_t index)
      -> typename impl_type::value_type&
    {
      return v_[index];
    }

    math_vector& operator +=(math_vector const& b)
    {
      for (std::size_t i = 0; i<N; ++i)
        v_[i] += b[i];
      return *this;
    }

  #ifndef DONT_USE_EXPR_TEMPL

    template <typename LE, typename Op, typename RE>
    math_vector(math_vector_expr<LE,Op,RE>&& mve)
    {
      for (std::size_t i = 0; i < N; ++i)
        v_[i] = mve[i];
      std::cout << this << ": math_vector(expr: " << &mve << ")" << std::endl;
    }

    template <typename RightExpr>
    math_vector& operator =(RightExpr&& re)
    {
      for (std::size_t i = 0; i<N; ++i)
        v_[i] = re[i];
      return *this;
    }

    template <typename RightExpr>
    math_vector& operator +=(RightExpr&& re)
    {
      for (std::size_t i = 0; i<N; ++i)
        v_[i] += re[i];
      return *this;
    }

    template <typename RightExpr>
    auto operator +(RightExpr&& re) const ->
      math_vector_expr<
        math_vector const&, 
        plus_op<typename impl_type::value_type>,
        decltype(std::forward<RightExpr>(re))
      >
    {
      return 
        math_vector_expr<
          math_vector const&, 
          plus_op<typename impl_type::value_type>, 
          decltype(std::forward<RightExpr>(re))
        >(
          *this, 
          std::forward<RightExpr>(re)
        )
      ;
    }

  #endif // #ifndef DONT_USE_EXPR_TEMPL

  private:
    impl_type v_;
};

//===========================================================================

template <std::size_t N>
inline void swap(math_vector<N>& a, math_vector<N>& b)
{
  a.swap(b);
}

//===========================================================================

#ifdef DONT_USE_EXPR_TEMPL

template <std::size_t N>
inline math_vector<N> operator +(
  math_vector<N> const& a, 
  math_vector<N> const& b
)
{
  math_vector<N> retval(a);
  retval += b;
  return retval;
}

template <std::size_t N>
inline math_vector<N> operator +(
  math_vector<N>&& a, 
  math_vector<N> const& b
)
{
  a += b;
  return std::move(a);
}

template <std::size_t N>
inline math_vector<N> operator +(
  math_vector<N> const& a, 
  math_vector<N>&& b
)
{
  b += a;
  return std::move(b);
}

template <std::size_t N>
inline math_vector<N> operator +(
  math_vector<N>&& a, 
  math_vector<N>&& b
)
{
  a += std::move(b);
  return std::move(a);
}

#endif // #ifdef DONT_USE_EXPR_TEMPL

//===========================================================================

template <std::size_t N>
std::ostream& operator <<(std::ostream& os, math_vector<N> const& mv)
{
  os << '(';
  for (std::size_t i = 0; i < N; ++i)
    os << mv[i] << ((i+1 != N) ? ',' : ')');
  return os;
}

//===========================================================================

int main()
{
  using namespace std;

  try
  {
    {
      cout << "CASE 1:\n";
      math_vector<3> a{1.0, 1.1, 1.2};
      math_vector<3> b{2.0, 2.1, 2.2};
      math_vector<3> c{3.0, 3.1, 3.2};
      math_vector<3> d{4.0, 4.1, 4.2};
      math_vector<3> result = a + b + c + d;
      cout << '[' << &result << "]: " << result << "\n";
    }
    cout << endl;
    {
      cout << "CASE 2:\n";
      math_vector<3> result =
        math_vector<3>{1.0, 1.1, 1.2} +
        math_vector<3>{2.0, 2.1, 2.2} +
        math_vector<3>{3.0, 3.1, 3.2} +
        math_vector<3>{4.0, 4.1, 4.2}
      ;
      cout << '[' << &result << "]: " << result << "\n";
    }
  }
  catch (...)
  {
    return 1;
  }
}

//===========================================================================

这篇关于表达式模板和C ++ 11的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆