Search code examples
c++copy-elisionreturn-value-optimization

Copy elision and operator overloading with C++


I have a struct such as:

struct A { 
    double x,y; 
    vector<double> vec; 
};

I would like to overload operators such as the plus operator so that I can perform operations such as:

A a,b,c,d;
//do work to set up the four structs. Then:
d = a + b + c;

Performance is important because these operations will be performed in 'inner loops' that are run very many times. I am concerned that the line (a + b + c) will be creating temporaries and so running constructors to 'A' unnecessarily. Running C++17, will the compiler definitely use copy elision to avoid creating temporaries? I need to run the line "d=a+b+c" without ever running the constructor to A.

I know that if I could definitely avoid temporaries by writing:

d = a;
d += b;
d += c;

However, practically, I am about to write a lot of code with long, mathematical lines, and it would be much more convenient to be able to write things all in one line (a + b + c), rather than have to break it down into a ton of "+=" lines.


Solution

  • Thank you to Joel Filho for the suggestion to use expression templates, and the reference to the relevant Wikipedia article. That approach in the Wikipedia article worked, although it had to be modified slightly for my particular case, because I am using named class members, instead of implementing a vector. Below is an implementation.

    template<typename E>
    class AExpression {
    public:
        AExpression() {};
    
        double Returna() const {
            return static_cast<E>(*this).Returna();
        };
        double Returnb() const {
            return static_cast<E>(*this).Returnb();
        };
    };
    
    struct A : public AExpression<A> {
        A() : a(kNaN), b(kNaN) { };
        double a,b;
        double Returna() const {
            return a;
        };
        double Returnb() const {
            return b;
        };
        
        template <typename E>
        A(AExpression<E> & expr) {
            a = expr.Returna();
            b = expr.Returnb();
        };
        
        //this method is needed because the return value of a line such as z = x + y is ASum, not A. This equality overload allows the code to convert back from type ASum to type A
        template <typename E>
        A & operator=(E const & expr) {
            a = expr.Returna();
            b = expr.Returnb();
            return *this;
        }
    };
    
    template <typename E1, typename E2>
    class ASum : public AExpression<ASum<E1, E2>>  {
        E1 const& one;
        E2 const& two;
    
    public:
        ASum(E1 const& onein, E2 const& twoin) : one(onein), two(twoin) { };
    
        double Returna() const {
            return one.Returna() + two.Returna();
        };
        double Returnb() const {
            return one.Returnb() + two.Returnb();
        };
    
    };
    
    template <typename E1, typename E2>
    ASum<E1, E2>
    operator+(AExpression<E1> const& u, AExpression<E2> const& v) {
       return ASum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
    }
    

    With that implementation, if I run the following lines, then it executes "z = x + y" without creating any temporaries.

    A x,y,z;
    x.a = 1;
    x.b = 2;
    y.a = 3;
    y.b = 4;
    
    z = x + y;
    //z is type 'A' and z.a = 4; z.b = 6; no temporaries are created in the line 'z = x + y'