2012-08-04 117 views
35

讓我們看的表達模板之一特別的益處:ET的可用於避免在存儲器矢量大小的臨時對象中出現的過載操作符,如:表達模板和C++ 11

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這個函數的返回語句應用移動語義。矢量的副本。這是一場勝利。

但是,如果我看一個簡單的語句,比如

d = a + b + c; 

我看到上面的函數被調用兩次(兩個operator+),而最終的分配可以通過移動語義來完成。

共執行2個循環。意思是我暫時放了一張,然後馬上閱讀。對於大型媒體而言,這會脫離緩存。這比表達式模板更糟糕。他們可以在一個循環中完成整個事情。專家組可以執行上述的等效代碼:

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

我想用移動語義或任何其他新的功能lambda表達式一起是否能夠像外星人一樣好。有什麼想法嗎?

編輯:

基本上,使用ET技術編譯器生成解析樹 類似於代數表達式與它的 類型系統。該樹由內部節點和葉節點組成。內部節點表示操作(加法,乘法等)和葉節點表示對數據對象的引用。

我試圖用一個 堆棧機器的方式來思考整個計算過程:從操作堆棧中取一個操作並從參數堆棧中取出下一個參數並評估操作。 將結果放回堆棧等待操作。

爲了表示這兩個不同的對象(操作堆棧和數據 葉棧)我捆綁在一起的操作的std::tuplestd::tuple用於數據離開成std::pair<>。最初,我 使用std:vector,但導致運行時間開銷。

整個過程分兩個階段:初始化操作和參數堆棧的堆棧機初始化 。並且通過將配對的容器 分配給矢量來觸發評估階段。

我創建的類Vec保持的私人array<int,5>(所述 有效載荷),並且設有一個重載的賦值運算符 取「表達」。

全球operator*超載爲採取 Vec和「表達」,使正確處理同樣的情況下 我們已經不僅僅是a*b更多的所有組合。(請注意,我換這個 教育爲例,乘法 - 基本上可以快速識別 的imull在彙編。)

什麼是評估前先進行啓動的「提取」的 值了參與Vec的對象並初始化參數 堆棧。這是必要的,以避免不同類型的對象圍繞着:可轉位向量和不可索引的結果。這就是 Extractor的用途。再好的事情:在這種情況下使用的變量模板導致沒有運行時開銷(所有這些都在編譯時間 完成)。

整件事情奏效。該表達式很好地評估(我也 添加了,但這是遺漏在這裏,以適應代碼)。在 以下,您可以看到彙編器輸出。只是原始計算,就像你想要的那樣:與ET技術相提並論。

Upshot。 C++ 11的新語言特性提供了可變參數 模板(與模板元編程一起)打開編譯時計算的 區域。我在這裏展示瞭如何使用 可變參數模板的優點來產生與傳統ET技術一樣好的代碼。

#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 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) 
+3

您應該查看[copy elision and RVO](http://en.wikipedia.org/wiki/Copy_elision)。此外,通過值傳遞其中一個向量而不是製作自己的'tmp'副本可能會有所幫助。 – juanchopanza 2012-08-04 13:39:15

+0

按價值傳遞無法幫助(仍然複製)。 (N)RVO無助於消除額外的循環 – ritter 2012-08-04 13:43:41

+0

@Frank:你確定嗎?這是RVO的強有力候選人。 – akappa 2012-08-04 13:47:43

回答

45

我想知道lambda表達式是否與移動語義或其他新特性一起可以做得如同ET一樣好。有什麼想法嗎?

快速解答

移動語義不靠自己--techniques如表達式模板(ETS)這樣仍然需要在C++ 11共萬能消除開銷如周圍移動數據!因此,在深入解答我的答案之前,快速回答您的問題,移動語義等不會完全取代ET,因爲我的答案如下所示。

詳細的解答

專家組通常返回代理對象推遲評估,直到後來,所以的C++ 11個語言特性沒有立即明顯的好處,直到觸發計算的代碼。也就是說,人們不想編寫ET代碼,但是,這會在代理構建表達式樹期間觸發運行時代碼生成。很好,C++ 11的移動語義和完美的轉發可以幫助避免這種情況發生。 (這在C++ 03中是不可能的。)

基本上,在編寫ET時,一旦想要利用語言特性來產生最優代碼,一旦涉及的代理對象的成員函數調用。在C++ 11中,這將包括使用完美的轉發,通過複製移動語義等,如果實際上仍然需要超過編譯器已經可以完成的工作。遊戲的名稱是爲了最小化生成的運行時代碼和/或最大化運行時速度和/或最小化運行時間開銷。

我想實際嘗試一些使用C++ 11功能的ET,以查看是否可以用a = b + c + d;表達式消除所有中間臨時實例類型。 (因爲這只是我正常活動的一個有趣的休息,所以我沒有將它與純粹使用C++ 03的ET代碼進行比較,也沒有擔心下面出現的代碼拋光的所有方面)。首先,我沒有使用lambda表達式 - 因爲我更喜歡使用顯式類型和函數 - 所以我不會爲你的問題爭論/反對lambda表達式。我的猜測是,它們會類似於使用函子,並且性能不如下面的非ET代碼更好(即,需要移動) - 至少編譯器可以使用它們自己的內部ET來自動優化lambda。然而,我寫的代碼利用了移動語義和完美的轉發。這是我從結果開始並最終呈現代碼的過程。

我創建了一個math_vector<N>類,其中N==3它定義了一個內部私有實例std::array<long double, N>。成員是默認構造函數,複製和移動構造函數和賦值,初始化程序列表構造函數,析構函數,swap()成員,operator []以訪問vector和operator + =的元素。使用而無需任何表達式模板,此代碼:

{ 
    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):

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項),result變量(即0x7fff8d6eddd0),並且還使附加的三個對象複製和移動。

爲了只着眼於臨時對象和移動,我創建了僅創建result作爲命名變量--all其它的是右值的第二情況下:

{ 
    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"; 
} 

此(再次當不使用ETS)輸出:

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() 

哪個更好:只創建額外的移動對象。

但我就想好了:我想零個額外的臨時變量,並有代碼,如果我用一個正常的編碼警告硬編碼的:所有的顯式實例類型仍然會創建(即四個initlist建設者和result )。要做到這一點,我再補充表情模板代碼如下:

  1. 代理math_vector_expr<LeftExpr,BinaryOp,RightExpr>類創建持有尚未計算表達式,
  2. 代理plus_op類是爲了保持加法運算,
  3. 一個構造函數被添加到math_vector以接受math_vector_expr對象,並且,
  4. 「starter」成員函數被添加以觸發表達式模板的創建。

使用ET的結果非常棒:在任何情況下都不需要額外的臨時對象!前兩例,現將以上輸出:

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一個的輸出之間的彙編代碼獲取這個美麗的彙編代碼字符串:

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++相似(甚至更小)輸出碼。沒有函數調用等 - 只是一堆補充,這正是人們想要的!

C++ 11的代碼如下。簡單地#define DONT_USE_EXPR_TEMPL不使用ET或根本不定義它使用ET。

#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; 
    } 
} 

//=========================================================================== 
+2

謝謝保羅的回答。在你的代碼中,你很好地應用了新的C++ 11技術淹沒的表達式模板技術 。做得好。我喜歡重載的操作類。是的,我同意lambdas基本上會呈現與命名結構相同的性能。我試圖繞過ET技術,如果你喜歡這個問題被我的代碼擴展。 – ritter 2012-08-05 17:41:46

+1

不客氣!你的問題也非常相關。新增加的內容(特別是可變參數模板,移動語義和完美轉發)實際上讓我們所有人都採取措施重新調整,並以更簡單的方式再次完成任務 - 就像我們開始編寫代碼一樣!也許很久以來,我們都在進行優化以有效地繞過編譯器。使用C++ 11時,不再需要這樣的代碼,只需讓編譯器完成它所做的工作,即可輕鬆實現更簡單的代碼,從而獲得更好/最佳的性能。所以在C++ 11中,我試圖提醒自己抵制優化的誘惑,但要更簡單地重新思考。 – 2012-08-05 18:44:32

+0

@Frank:我認爲這個答案已經準備好被接受,不是嗎? – marton78 2013-02-12 14:14:46

6

這是Paul Preney的代碼的修正版本。我通過電子郵件和評論通知了作者;我編寫了一個編輯,但它已被不合格的審閱者拒絕。

原始代碼中的錯誤是math_vector_expr :: operator +的BinaryOp模板參數是固定的。

#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 T> struct plus_op; 


template < 
    typename LeftExpr, 
    typename BinaryOp, 
    typename RightExpr 
> 
class math_vector_expr 
{ 
    public: 
    typedef typename std::remove_reference<LeftExpr>::type::value_type value_type; 

    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&, 
     plus_op<value_type>, 
     decltype(std::forward<RE>(re)) 
     > 
    { 
     return 
     math_vector_expr< 
      math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&, 
      plus_op<value_type>, 
      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 -> 
     value_type 
    { 
     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: 
    typedef typename impl_type::value_type value_type; 

    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<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 
     -> value_type const& 
    { 
     return v_[index]; 
    } 

    auto operator [](std::size_t index) 
     -> 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<value_type>, 
     decltype(std::forward<RightExpr>(re)) 
     > 
    { 
     return 
     math_vector_expr< 
      math_vector const&, 
      plus_op<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; 
    } 
} 

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