2

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.

2
  • 1
    If your operator+ requires a temporary to hold the result of a + b, then you're still going to be calling constructors. Commented Jun 14, 2021 at 18:17
  • look into expression templates. I doubt that copy elision can make expression templates obsolete Commented Jun 14, 2021 at 18:31

2 Answers 2

2

As a commenter suggested, if your operator+ requires a temporary, you still construct a vector and return it, even if NRVO'd.

But, if you want to do it like that, you can reduce the amount of temporaries created:

  1. Create an rvalue-qualified operator+ implementation
  2. Use the move-assignment operator to move the resulting vector into d, or the move constructor to make the temporary into d without having to

Consider this:

A operator+(A& a, const A& b){
    A temp = /*...*/;
    return temp; // NRVO
}

A operator+(A&& a, const A& b){
    // We know a is temporary, so we can move its guts to a new one.
    a += b;
    return std::move(a); // In this case, we require the move, as it's not NRVO
}

// Then use it:
A d = a + b + c;
// What this does: 
// 1. Calls `a + b` and creates a temporary, as both are lvalues
// 2. Utilizes the same temporary to put the result of (a+b) + c
// 3. Still utilizes the same temporary to materialize the rvalue into d
Sign up to request clarification or add additional context in comments.

5 Comments

Very interesting. Thank you for sharing the idea. Still, it sounds like this method would still create one temporary? I really need to avoid creating any temporaries (to guarantee that, worst case, I could revert to (inconveniently) using lines with '+='). Note that my value 'd' will have been created previously.
Yes, this will create a temporary, but that's only relevant if d's vector is already allocated to the correct size, thus increasing the number of allocations in the series of calls. If d would need to copy a, like in your example, you have the exact same number of allocations as move assignment (the temporary allocates, but moving into d doesn't)
Right, but the problem is that d would already be allocated to the correct size. I'll create d once, store it, and reuse it over and over for calculations so that I don't have to do lots of reallocations. Creating a temporary would force a new heap allocation to resize the vector every time to match the other objects, which could become expensive for these functions if they are run many times.
Then you should look at expression templates, like the other commenter pointed out. The Wikipedia example is adding 3 vectors without allocating a temporary, just like your example.
Yes, I think that you are right. I experimented with that today, and got it working. The Wikipedia example is not one-to-one identical to what I am dong, because I am doing more than just writing a Vec class. But with a bit of experimenting, got it to work. Thanks! I will write a comment soon when I have time to copy over the actual code that I wrote to solve this exact problem.
0

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'

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.