rvalues and lvalues

546 views
Skip to first unread message

Jerry

unread,
Nov 3, 2011, 2:15:02 PM11/3/11
to
How do I write a function that will accept both rvalues and lvalues?

For example, if I write this (contrived example):

#include <iostream>
#include <vector>
std::vector<int> & operator << (std::vector<int> & x, const
std::vector<int>::value_type & y)
{
x.push_back(y);
return x;
}
int main()
{
std::cout << (std::vector<int>() << 1)[0] << std::endl;
}

Then I get:
Dynamo.cpp: In function 'int main()':
Dynamo.cpp:12:41: error: no match for 'operator<<' in
'std::vector<int>() << 1'
Dynamo.cpp:12:41: note: candidates are:
Dynamo.cpp:4:20: note: std::vector<int>& operator<<(std::vector<int>&,
const value_type&)
Dynamo.cpp:4:20: note: no known conversion for argument 1 from
'std::vector<int>' to 'std::vector<int>&'

But If I change the code to:

#include <iostream>
#include <vector>
std::vector<int> operator << (std::vector<int> x, const
std::vector<int>::value_type & y)
{
x.push_back(y);
return x;
}
int main()
{
std::vector<int> a;
a << 1;
std::cout << a[0] << std::endl;
}

Then I get a segmentation fault because the vector was copied and the
change was made to the copy so the original has no 0th element.

How do I write such a thing that will accept both lvalues and rvalues?

Thanks,

Jerry


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

Daniel Krügler

unread,
Nov 3, 2011, 7:50:08 PM11/3/11
to
Am 03.11.2011 19:15, schrieb Jerry:
> How do I write a function that will accept both rvalues and lvalues?
>
> For example, if I write this (contrived example):
>
> #include<iostream>
> #include<vector>
> std::vector<int> & operator<< (std::vector<int> & x, const
> std::vector<int>::value_type& y)
> {
> x.push_back(y);
> return x;
> }
> int main()
> {
> std::cout<< (std::vector<int>()<< 1)[0]<< std::endl;
> }
>
> Then I get:
> Dynamo.cpp: In function 'int main()':
> Dynamo.cpp:12:41: error: no match for 'operator<<' in
> 'std::vector<int>()<< 1'
> Dynamo.cpp:12:41: note: candidates are:
> Dynamo.cpp:4:20: note: std::vector<int>& operator<<(std::vector<int>&,
> const value_type&)
> Dynamo.cpp:4:20: note: no known conversion for argument 1 from
> 'std::vector<int>' to 'std::vector<int>&'
>
> But If I change the code to:
>
> #include<iostream>
> #include<vector>
> std::vector<int> operator<< (std::vector<int> x, const
> std::vector<int>::value_type& y)
> {
> x.push_back(y);
> return x;
> }
> int main()
> {
> std::vector<int> a;
> a<< 1;
> std::cout<< a[0]<< std::endl;
> }
>
> Then I get a segmentation fault because the vector was copied and the
> change was made to the copy so the original has no 0th element.
>
> How do I write such a thing that will accept both lvalues and rvalues?

Either add an overload that takes an rvalue-reference, like so:

std::vector<int> & operator<<(std::vector<int> && x,
const std::vector<int>::value_type & y)
{
x.push_back(y);
return x;
}


or replace the function by a (possibly constrained) function template that uses perfect forwarding:

[your includes]
#include <type_traits>

template<class T>
struct is_vector_impl : std::false_type {};

template<class T, class A>
struct is_vector_impl<std::vector<T, A>> : std::true_type {};

template<class T>
struct is_vector : is_vector_impl<typename std::remove_reference<T>::type>
{
};

template<class T,
class = typename std::enable_if<is_vector<T>::value>::type
>
std::vector<int> & operator<<(T&& x,
const std::vector<int>::value_type & y)
{
x.push_back(y);
return x;
}

HTH & Greetings from Bremen,

Daniel Kr�gler

Marc

unread,
Nov 3, 2011, 7:51:44 PM11/3/11
to
Jerry wrote:

> How do I write a function that will accept both rvalues and lvalues?
>
> For example, if I write this (contrived example):
>
> #include <iostream>
> #include <vector>
> std::vector<int> & operator << (std::vector<int> & x, const
> std::vector<int>::value_type & y)
> {
> x.push_back(y);
> return x;
> }
> int main()
> {
> std::cout << (std::vector<int>() << 1)[0] << std::endl;
> }
>
> Then I get:
> Dynamo.cpp: In function 'int main()':
> Dynamo.cpp:12:41: error: no match for 'operator<<' in
> 'std::vector<int>() << 1'

Some random ideas:

I think the best is to write 2 functions, where one (&&) forwards to
the other (&).

Fancier, ask as first argument a type that is constructible from
vector& and vector&& and stores a pointer to it (storing references is
asking for trouble). Sadly, even with an implicit conversion to
vector&, it forces you to change the code of the function.

Uglier, use templates, pass as first argument a T&&, and use sfinae to
restrict the possible T.

Ugliest: take a vector<int> const& and const_cast it.

There are probably other ways...

Andrzej Krzemieński

unread,
Nov 3, 2011, 7:53:26 PM11/3/11
to
On Nov 3, 7:15 pm, Jerry <jerry.jerem...@gmail.com> wrote:
> How do I write a function that will accept both rvalues and lvalues?
>
> For example, if I write this (contrived example):
>
> #include <iostream>
> #include <vector>
> std::vector<int> & operator << (std::vector<int> & x, const
> std::vector<int>::value_type & y)
> {
> x.push_back(y);
> return x;}

Hi,
If your compiler already supports r-value references, you could define
two similar functions that differ by argument type:

vector<int> & operator << ( vector<int> & x, int y ){
return x.push_back(y), x;
}

vector<int> operator << ( vector<int> && x, int y ){
return move(x) << y;
}

Regards,
&rzej

Andrzej Krzemieński

unread,
Nov 4, 2011, 7:37:52 PM11/4/11
to
> vector<int> & operator << ( vector<int> & x, int y ){
> return x.push_back(y), x;
> }
>
> vector<int> operator << ( vector<int> && x, int y ){
> return move(x) << y;
> }

Sorry, it should read:

vector<int> & operator << ( vector<int> & x, int y ){
return x.push_back(y), x;
}

vector<int> operator << ( vector<int> && x, int y ){
return x << y;

Arne Mertz

unread,
Nov 7, 2011, 2:52:38 PM11/7/11
to
On Nov 4, 12:50 am, Daniel Krügler <daniel.krueg...@googlemail.com>
wrote:
Hi Daniel, wouldn't your implementation remove the rvalue-ness from
the first argument?
You could write something like this:

int main()
{
std::vector<int>& ivr = std::vector<int>() << 5;
ivr << 6 << 7;
}

This binds a non-const lvalue-ref to an rvalue, followed by happily
crashing the application...

Regards, Arne

Daniel Krügler

unread,
Nov 7, 2011, 6:33:01 PM11/7/11
to
Am 07.11.2011 20:52, schrieb Arne Mertz:
> On Nov 4, 12:50 am, Daniel Krügler<daniel.krueg...@googlemail.com>
> wrote:
>> Am 03.11.2011 19:15, schrieb Jerry:
>
>> template<class T,
>> class = typename std::enable_if<is_vector<T>::value>::type
>>
>> std::vector<int> & operator<<(T&& x,
>> const std::vector<int>::value_type& y)
>> {
>> x.push_back(y);
>> return x;
>>
>> }
>>
>
> Hi Daniel, wouldn't your implementation remove the rvalue-ness from
> the first argument?

It would, but I intentionally suggested to do that in this case
nonetheless assuming that the OP intended one of the rare use cases,
where this makes sense as in:

template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

You *could* use this in a wrong way, but I would argue that this so rare
that the advantages of this idiom win over the possible risk of misusage.

> You could write something like this:
>
> int main()
> {
> std::vector<int>& ivr = std::vector<int>()<< 5;
> ivr<< 6<< 7;
> }
>
> This binds a non-const lvalue-ref to an rvalue, followed by happily
> crashing the application...

Certainly, and I should probably have suggested to the OP *not* to add
such an overload anyway (It causes problems all the way, because the
operator overload cannot be added to namespace std with all the
follow-up problems in regard to ADL). But then I looked at the problem
as a variant of the IO insertion and suggested a similar solution. So, I
should finish with: "No, don't add such overloads to your code unless
you are just playing with the language and don't do serious code
writing" ;-)

Greetings from Bremen,

Daniel Krügler

Frank Birbacher

unread,
Dec 7, 2011, 5:40:50 PM12/7/11
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

Am 05.11.11 00:37, schrieb Andrzej Krzemieński:
> vector<int> & operator << ( vector<int> & x, int y ){
> return x.push_back(y), x;
> }
>
> vector<int> operator << ( vector<int> && x, int y ){
> return x << y;
> }

This is the part where I'm still lost: it is hard to see why the latter
function does not call itself recursively.

I understand:
The second operator is called for unnamed values as first parameter
(temporaries). Such a value is bound to the & of the first operator and
the object is filled with a value. This is why the second operator must
not return a reference &. It returns by value instead.

Questions:
1. Why does the latter not call itself?
2. Should the latter one read:
return std::move(x << y);
?

Frank
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.12 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk7f4OwACgkQhAOUmAZhnmoOZQCcD3DEZRQF702D7aCeGtfxE3bH
IEQAni5E5FYt1UCvqFxtBUuP6oREA7dw
=yT+6
-----END PGP SIGNATURE-----

Daniel Krügler

unread,
Dec 8, 2011, 12:25:51 PM12/8/11
to
On 2011-12-07 23:40, Frank Birbacher wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Hi!
>
> Am 05.11.11 00:37, schrieb Andrzej Krzemieński:
>> vector<int> & operator<< ( vector<int> & x, int y ){
>> return x.push_back(y), x;
>> }
>>
>> vector<int> operator<< ( vector<int> && x, int y ){
>> return x<< y;
>> }
>
> This is the part where I'm still lost: it is hard to see why the latter
> function does not call itself recursively.
>
> I understand:
> The second operator is called for unnamed values as first parameter
> (temporaries).

More precisely: It is called, when the argument is a (mutable) rvalue. I
emphasize this, because the overload selection/reference binding rules
are based on *value categories*.

> Such a value is bound to the& of the first operator and
> the object is filled with a value. This is why the second operator must
> not return a reference&. It returns by value instead.
>
> Questions:
> 1. Why does the latter not call itself?

Because x is an lvalue! Yes, this seems astonishing at first, when you
compare this view with the view of the "external caller". It is much
easier to get used to it, when you realize that not alone the type
determines whether something is an lvalue or an rvalue. This means that
just having an rvalue reference of something does mean that the
corresponding expression is an rvalue.

This in some aspects astonishing consequence has indeed the following
advantages:

a) Whether an expression is an lvalue or an rvalue is more or less
determined whether you have a *named* variable or not (Note: Variables
include references in C++11), so there *is* some pattern here.

b) It helps programmers to recognize when a potentially mutable
operation will happen when working with a variable, it helps to prevent
those traps that you could run into with types like std::auto_ptr!

Consider:

void foo(std::vector&& x) {
std::vector y = x; // Assume this would move the contents
// of x into y...
if (x.size() > 2) { .. } // x is used as if it where unchanged..
};

This is the typical "auto_ptr-gotcha" problem! The rules are defined
such that x is an lvalue in the construction of y to prevent subtle
errors to occur. It is unclear what was intended here. To make that
clearer, user call should explicitly call std::move (or any moral
equivalent) to make clear what they want:

void foo(std::vector&& x) {
std::vector y = std::move(x); // Yes, I want to modify x!
if (x.size() > 2) { .. } // Oops, I must have overlooked
// something here...
};

> 2. Should the latter one read:
> return std::move(x<< y);
> ?

Yes, this is correct. The expression "x << y" is an lvalue and applying
std::move to it in this case ensures move semantics for the return
statement.

HTH & Greetings from Bremen,

Daniel Krügler

Andrzej Krzemieński

unread,
Dec 8, 2011, 12:26:27 PM12/8/11
to
> > vector<int> & operator << ( vector<int> & x, int y ){
> > return x.push_back(y), x;
> > }
>
> > vector<int> operator << ( vector<int> && x, int y ){
> > return x << y;
> > }
>
> This is the part where I'm still lost: it is hard to see why the latter
> function does not call itself recursively.
>
> I understand:
> The second operator is called for unnamed values as first parameter
> (temporaries). Such a value is bound to the & of the first operator and
> the object is filled with a value. This is why the second operator must
> not return a reference &. It returns by value instead.
>
> Questions:
> 1. Why does the latter not call itself?

This has to do with the nature of rvalue references. Rvalue reference
(in our case: x) binds to rvalues, but it is itself an lvalue. A named
reference (even an rvalue reference) is an lvalue. Therefore
expression x << y picks an lvalue overload.


> 2. Should the latter one read:
> return std::move(x << y);
> ?

Yes, it should - for performance reasons (in order to pick the move
constructor, rather than copy constructor, of std::vector). This is my
mistake.

BTW, here is the post where I tried to explain some aspects of rvalues
and lvalues:
http://akrzemi1.wordpress.com/2011/11/09/lvalues-rvalues-and-references/

Regards,
&rzej

Frank Birbacher

unread,
Dec 9, 2011, 12:56:26 AM12/9/11
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

Am 08.12.11 18:25, schrieb Daniel Krügler:
> On 2011-12-07 23:40, Frank Birbacher wrote:
>> Questions:
>> 1. Why does the latter not call itself?
>
> Because x is an lvalue!

Am 08.12.11 18:26, schrieb Andrzej Krzemieński:
> This has to do with the nature of rvalue references. Rvalue reference
> (in our case: x) binds to rvalues, but it is itself an lvalue. A named
> reference (even an rvalue reference) is an lvalue. Therefore
> expression x << y picks an lvalue overload.

Ah, I see. The point I was missing is: it's not all about the type of x,
but actually about the static property of the expression involved. Now
that I lost my misconception everything fits together.

Am 08.12.11 18:25, schrieb Daniel Krügler:
> Consider:
>
> void foo(std::vector&& x) {
> std::vector y = x; // Assume this would move the contents
> // of x into y...
> if (x.size() > 2) { .. } // x is used as if it where unchanged..
> };
>
> This is the typical "auto_ptr-gotcha" problem!
[snip]
> void foo(std::vector&& x) {
> std::vector y = std::move(x); // Yes, I want to modify x!

For assigning the rvalue reference I expected a move to occur. The
std::move looked redundant to me. Now I see the need. It's not there to
change the type of x, but to turn it into an rvalue expression.

>> 2. Should the latter one read:
>> return std::move(x<< y);
>> ?
>
> Yes, this is correct. The expression "x << y" is an lvalue and applying
> std::move to it in this case ensures move semantics for the return
> statement.

Ok, I guess today I have successfully used this technique to correctly
return a unique_ptr member variable from a member function.

Am 08.12.11 18:26, schrieb Andrzej Krzemieński:
> BTW, here is the post where I tried to explain some aspects of rvalues
> and lvalues:
> http://akrzemi1.wordpress.com/2011/11/09/lvalues-rvalues-and-references/

Great, I've read both your article and Bjarne's.

Thank you both!
Frank
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.12 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk7hCmgACgkQhAOUmAZhnmo9SgCfRzZilRZMZhZZoUTV8xGOf7SZ
l+4AnRj1D7iU58Vb9Yjqjkr6SPG6bC1a
=bsoO
-----END PGP SIGNATURE-----

SG

unread,
Dec 9, 2011, 12:27:56 PM12/9/11
to
On Dec 9, 6:56 am, Frank Birbacher wrote:
> [...]
> Ok, I guess today I have successfully used this technique to correctly
> return a unique_ptr member variable from a member function.

If you want to be sure about it, post an example. ;-)

I'm responding because I'd like to stress that std::move should only
be used when necessary because in some cases it's actually a bit
counter productive. Example:

unique_ptr<int> source() {
unique_ptr<int> p (new int(42));
return std::move(p);
}

Here, the std::move call is not needed. Even though, p is an lvalue
expression, the compiler knows that it refers to a local object which
is about to be destroyed right on exiting the function. So, in this
case, the compiler will automatically consider a move construction.
There is something even better than a move construction: a copy/move
elision (more specifically in this case: NRVO = named return value
optimization). The use of std::move actually inhibits this
optimization. So, prefer to write

unique_ptr<int> source() {
unique_ptr<int> p (new int(42));
return p;
}

instead (without std::move). :-)

Cheers!
SG
Reply all
Reply to author
Forward
0 new messages