Hello,
I'm trying to understand a lot about the new stuff in C++ 2011 myself,
so please take my answers below with a grain of salt.
On Oct 2, 4:23 pm, fmatthew5876 <
fmatthew5...@googlemail.com> wrote:
> Suppose I have this:
>
> class Matrix4 {
> public:
> //stuff
> private:
> float _m[16];
> };
>
> Matrix4 operator+(Matrix4 l, const Matrix4& r) {
> l += r;
> return l;
> }
>
> Is there any possible scenario where I would want to also create
> a version of operator+ that uses rvalue references? i.e.
>
> Matrix4 operator+(Matrix4 l, const Matrix4&& r) {
> l+=r;
Here I think you mean "l += std::move(r);" otherwise operator+= will
get r by const reference (presumably) rather than by rvalue
reference. That won't make any difference in this case, though, as
you fear. (And I'm not sure one can pass a const reference into
std::move().) But one can do better, see below.
> return l;
>
> }
>
> I understand rvalue references are desirable in most cases because we
> want to use move constructors instead of making unnecessary copies.
> But in this particular case, the matrix class directly contains its
> data instead of doing an allocation so there is no move constructor.
>
> To put it more succinctly, is there ever a case when the first operator+
> will create a copy for the second argument where the second operator+
> would not?
Not as written, because as you have it, r is a dead end ... its
lifetime as a temporary ends inside this function. It looks like what
you really hoped to do was take *l* as an rvalue reference. Then it
can be re-used by also *returning* an rvalue reference, which means no
copying happens at the function return point, until you finally put
the result into an lvalue in calling code:
Matrix4 && operator + (Matrix4 && l, const Matrix4 & r)
{ l += r; return std::move(l); }
(Now, most compilers implement named return value optimization, so you
might not see any copies even if 'l' was both taken and returned by
value. But this function definition takes away that uncertainty.)
Since matrix addition (and, fortunately, floating-point addition) is
symmetric, you'd probably want to implement both cases:
Matrix4 && operator + (const Matrix4 & l, Matrix4 && r)
{ r += l; return std::move(r); }
And I think you also need to implement the double-rvalue version
explicitly, or the compiler may complain about the ambiguity if both
inputs are temporaries?
Matrix4 && operator + (Matrix4 && l, Matrix4 && r)
{ l += r; return std::move(l); /* choose one randomly */ }
Finally, your original version is still needed in case both input
matrices are lvalues. Again, my apologies as I'm not sure whether
your original signature will be ambiguous with respect to the above
other three, so I rewrite it here explicitly with const references:
Matrix4 && operator + (const Matrix4 & l, const Matrix4 & r)
{ return Matrix4(l) + r; /* call first rvalue ref version */ }
If I understand how all this works, the following code (assuming a, b,
c, d are all Matrix4's) should produce only one temporary copy, the
one explicitly written out in the last version of the operator above;
in addition to the unavoidable copy (though it might be elided by the
compiler?) from the temporary result of the entire RHS into the lvalue
'result'.
Matrix4 a, b, c, d;
// set a, b, c, d...
Matrix4 result = a + b + c + d;
But if you parenthesize terms as follows, there will be two
temporaries created, so just having move-enabled code is still not a
panacea. One does still need to think consciously about when
temporaries will appear:
Matrix4 result2 = (a + b) + (c + d);
And one can still rewrite the code at a slight clarity loss to prevent
any temporaries at all, bypassing all the rvalue-ref enabled code I
just wrote:
Matrix4 result3{a}; result3 += b; result3 += c; result3 += d;
Happy to be corrected, as always.
- Kevin B. McCarty