This will perform 1 copy construction and 3 move constructions.
- Make a copy of
a
to bind to lhs
.
- Move construct
lhs
out of the first +
.
- The return of the first
+
will bind to the by value lhs
parameter of the second +
with elision.
- The return of the second
lhs
will incur the second move construction.
- The return of the third
lhs
will incur the third move construction.
- The temporary returned from the third
+
will be constructed at sum
.
For each of the move constructions described above, there is another move construction that is optionally elided. So you are only guaranteed to have 1 copy and 6 moves. But in practice, unless you -fno-elide-constructors
, you will have 1 copy and 3 moves.
If you don't reference a
after this expression, you could further optimize with:
X sum = std::move(a) + b + c + d;
resulting in 0 copies and 4 moves (7 moves with -fno-elide-constructors
).
The above results have been confirmed with an X
which has instrumented copy and move constructors.
Update
If you're interested in different ways to optimize this, you could start with overload the lhs on X const&
and X&&
:
friend X operator+(X&& lhs, X const& rhs) {
lhs += rhs;
return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
auto temp = lhs;
temp += rhs;
return temp;
}
This gets things down to 1 copy and 2 moves. If you are willing to restrict your clients from ever catching the return of your +
by reference, then you can return X&&
from one of the overloads like this:
friend X&& operator+(X&& lhs, X const& rhs) {
lhs += rhs;
return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
auto temp = lhs;
temp += rhs;
return temp;
}
Getting you down to 1 copy and 1 move. Note that in this latest design, if you client ever does this:
X&& x = a + b + c;
then x
is a dangling reference (which is why std::string
does not do this).
a+b
directly intooperator+(??, c)
? – Fraternity