Expression templates and C++11

后端 未结 2 469
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-12 11:28

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:

相关标签:
2条回答
  • 2020-12-12 12:09

    Here is the corrected version of Paul Preney's code. I have informed the author by email and in comments; I have written an edit, but it has been rejected by unqualified reviewers.

    The error in the original code is that BinaryOp template parameter of math_vector_expr::operator+ is fixed.

    #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;
      }
    }
    
    //===========================================================================
    
    0 讨论(0)
  • 2020-12-12 12:14

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

    Quick Answer

    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.

    Detailed Answer

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

    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.

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

    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.

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

    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)
    0x7fff8d6effffd0: math_vector(move: 0x7fff8d6edda0)
    0x7fff8d6edda0: ~math_vector()
    0x7fff8d6edd70: ~math_vector()
    [0x7fff8d6effffd0]: (10,10.4,10.8)
    0x7fff8d6effffd0: ~math_vector()
    0x7fff8d6ede30: ~math_vector()
    0x7fff8d6ede90: ~math_vector()
    0x7fff8d6edef0: ~math_vector()
    0x7fff8d6edf50: ~math_vector()
    

    i.e., the four explicit constructed instances using initializer lists (i.e., the initlist items), the result variable (i.e., 0x7fff8d6effffd0), 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";
    }
    

    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.

    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. 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.

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

    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)
    

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

    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;
      }
    }
    
    //===========================================================================
    
    0 讨论(0)
提交回复
热议问题