Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

rvalue references and parameter passing

217 views
Skip to first unread message

fmatthew5876

unread,
Oct 2, 2012, 7:23:27 PM10/2/12
to
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;
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?


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

SG

unread,
Oct 3, 2012, 12:20:10 PM10/3/12
to
Am Mittwoch, 3. Oktober 2012 01:23:30 UTC+2 schrieb fmatthew5876:
> 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;
> return l;
> }

No, not really -- not with this class.

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

Right.

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

No. None that I can think of. Rvalue references don't magically avoid
copies. They are just means to hold on to "temporary" objects with write
access to them. The magic of movable types happens in their respective
move operations (ctor, assignment, etc).

Cheers!
SG

Daniel Krügler

unread,
Oct 3, 2012, 2:27:38 PM10/3/12
to
Am 03.10.2012 01:23, schrieb fmatthew5876:
> 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;
> return l;
> }

I cannot imagine one in the scenario you presented us here. There is
no advantage of adding this overload, because you don't perform
anything different here compared to your first overload.

> I understand rvalue references are desirable in most cases because
> we want to use move constructors instead of making unnecessary
> copies.

This is surely the most prominent use-case, I agree.

> But in this particular case, the matrix class directly contains its
> data instead of doing an allocation so there is no move constructor.

So why adding overloads without any advantage?

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

Since the second operation is not different from the first (except
that it will only be called, when the argument is an rvalue), I don't
see any reason why it should help you saving copies.

HTH & Greetings from Bremen,

Daniel Kr�gler

Kevin McCarty

unread,
Oct 3, 2012, 8:22:54 PM10/3/12
to
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

fmatthew5876

unread,
Oct 4, 2012, 12:25:35 AM10/4/12
to
I ran some tests which some might find interesting. It turns out the
best version of a matrix multiply like this is below. I found this
interesting because of all of the articles out there talking about the
merits of passing by value. In most cases pass by value and rvalue
reference overloads are useful when you have move constructors, but in
this case there is no concept of a move constructor.

Anyway the best version is this:
Matrix4 operator+(const Matrix4& l, const Matrix4 & l) {
Matrix4 m(l);
m+= r;
return m;
}

I tested 4 cases by replacing the copy constructor with a print
statement and running the following:

Matrix4 m, n, x;

x = m + n;

x = m + Matrix4();

x = Matrix4() + n;

x = Matrix4() + Matrix4();

In all 4 cases, the const reference version does 1 copy. The reason is
because of Return value optimization. In all cases the only copy is
when we do m(l); The compiler is smart enough to construct the return
value directly into x.

If you do this version:
Matrix4 operator+(Matrix4 l, const Matrix4& r) {
l+= r;
return l;
}

Sometimes you get 2 copies. This happens in cases 1 and 2 because
return value optimization does not work when you return one of the
function parameters. So first the lvalue is copied into l, then the
return value is copied again into x.

One final note, don't do this:

Matrix4 operator+(const Matrix4& l, const Matrix& r) {
Matrix4 m(l);
return m+= r;
}

You might think you are being clever, but what you're returning now is
the return value of operator+= which is a Matrix4 reference. The
compiler has no way of knowing what this reference points to so it
cannot do RVO.

Dave Harris

unread,
Oct 5, 2012, 1:19:47 AM10/5/12
to

kmcc...@googlemail.com (Kevin McCarty) wrote (abridged):
> Matrix4 && operator + (Matrix4 && l, const Matrix4 & r)
> { l += r; return std::move(l); }

This looks like it returns a reference to a temporary, which is asking
for trouble.


> Matrix4 && operator + (const Matrix4 & l, const Matrix4 & r)
> { return Matrix4(l) + r; /* call first rvalue ref version */ }

Again this is returning a reference to a temporary. Here it is clear
that it is the object "Matrix4(l)" that we are returning a reference
to, and it will be destroyed before the operator's result can be used.

-- Dave Harris, Nottingham, UK.

Kevin McCarty

unread,
Oct 5, 2012, 3:42:40 PM10/5/12
to

Apologies, Dave Harris, I'm rearranging the order of your post in my
quoting below...

On Oct 4, 9:20 pm, brang...@cix.compulink.co.uk (Dave Harris) wrote:
> kmcca...@googlemail.com (Kevin McCarty) wrote (abridged):
> > Matrix4 && operator + (const Matrix4 & l, const Matrix4 & r)
> > { return Matrix4(l) + r; /* call first rvalue ref version */ }
>
> Again this is returning a reference to a temporary. Here it is clear
> that it is the object "Matrix4(l)" that we are returning a reference
> to, and it will be destroyed before the operator's result can be
> used.

Yes, you're right of course, my apologies. That version is going to
have to return a copy by value, for instance as fmatthew5876 notes
else-thread.


> > Matrix4 && operator + (Matrix4 && l, const Matrix4 & r)
> > { l += r; return std::move(l); }
>
> This looks like it returns a reference to a temporary, which is
> asking for trouble.

Here I still believe it could be fine: the temporary that gets
returned from this version is the same temporary that was originally
passed in, isn't it?

That is, considering the following code:

Matrix4 a, b, c;
Matrix4 result = a + b + c;

First 'a + b' is computed via the two-lvalue version of operator+,
yielding a temporary. That temporary will survive until the sequence
point at the closing ';' above.

When that temporary then has 'c' added to it, the temporary will
become the LHS input to operator+(Matrix4 && tmp, const Matrix4 & c),
which modifies its LHS and return another reference to it.

Finally, the resulting temporary (still the same one) will be copied
into the Matrix4 named 'result' before being destroyed at the next
sequence point.

Am I misunderstanding how this works?

Of course a caller who was unaware of the reference return type might
instead write

const Matrix4 & result = a + b + c;

and end up with a dangling reference. Is this where the trouble comes
from in your opinion? Is it just too unsafe to do this in real-world
code, so that even in C++ 2011 we can take full advantage of modifying
temporary objects only when their class type is built mainly from
stuff allocated with new[]?

Thanks,

- Kevin B. McCarty

Gene Bushuyev

unread,
Oct 6, 2012, 2:28:07 AM10/6/12
to
{ Reformatted; please limit your lines to 70 characters,
and do not insert empty lines in quoted sections. -mod }

On Tuesday, October 2, 2012 4:23:30 PM UTC-7, fmatthew5876 wrote:
> Suppose I have this:
>
> class Matrix4 {
> public:
> //stuff
> private:
> float _m[16];
> };
>
> Matrix4 operator+(Matrix4 l, const Matrix4& r) {
> l += r;
> return l;
> }

The Matrix4 class has no move semantics, built-in types are always
copied. But assuming you modified it to make use of moves (simplified
version):

class Matrix4 {
float* m;
public:
Matrix4() : m(new float[16]) {}
Matrix4(const Matrix4& other) : m(new float[16])
{ std::copy(other.m, other.m+16, m); std::cout << "\ncopy"; }
Matrix4(Matrix4&& other) : m(other.m) { other.m = 0; std::cout <<
"\nmove"; }
~Matrix4() { delete[] m; }
Matrix4& operator+= (const Matrix4& other)
{
for(auto i = 0; i < 16; ++i) { m[i] += other.m[i]; }
return *this;
}
};

Matrix4 operator+ (Matrix4 l /* move from temporary*/, const Matrix4& r)
{
std::cout << "\noperator+(Matrix4, const Matrix4&)";
// move explicitly, because += returns lvalue reference
return std::move(l += r);
}

Unfortunately this solution is not general enough, it only handles the
cases of lvalue+lvalue, rvalue+lvalue, rvalue+rvalue, but
lvalue+rvalue is inefficient. The following overload takes care of
lvalue+rvalue case (Matrix&& has higher priority than const Matrix& in
selecting best viable function):

Matrix4 operator+ (const Matrix4& l, Matrix4&& r)
{
std::cout << "\nnoperator+(const Matrix4&, Matrix4&&)";
return std::move(r += l); // move explicitly
}

Here you can find the test: http://ideone.com/XV6BY
>
> 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;
> return l;
> }

No reason to create this one, it doesn't do anything different.

Dave Harris

unread,
Oct 7, 2012, 8:57:09 AM10/7/12
to
kmcc...@googlemail.com (Kevin McCarty) wrote (abridged):
> Of course a caller who was unaware of the reference return type
> might instead write
>
> const Matrix4 & result = a + b + c;
>
> and end up with a dangling reference. Is this where the trouble
> comes from in your opinion?

Yes.


> Is it just too unsafe to do this in real-world code, so that even
> in C++ 2011 we can take full advantage of modifying temporary
> objects only when their class type is built mainly from
> stuff allocated with new[]?

That sounds reasonable to me.

In practice, return value optimisations are often sufficient. In this
case we could probably write:

Matrix4 operator+( const Matrix4 &lhs, const Matrix4 &rhs ) {
Matrix4 result;
result._m[0] = lhs._m[0] + rhs._m[0];
// ...
return result;
}

and avoid redundant copying.

-- Dave Harris, Nottingham, UK.


0 new messages