Pass by rvalue reference

75 views
Skip to first unread message

Pawel Por

unread,
Jan 25, 2023, 3:43:15 PMJan 25
to
Hello,

Please help me to understand why in the following example MyList::push_front(T&&) receives argument by lvalue reference while std::forward_list::push_front(_Tp&&) receives argument by rvalue reference.

Thank you in advance

#include <iostream>
#include <forward_list>

template <typename T>
void p(const T &v)
{
std::cout << v << std::endl;
}

struct Dog
{
Dog() { p(__PRETTY_FUNCTION__); }
Dog(const Dog&) { p(__PRETTY_FUNCTION__); }
Dog(Dog&&) { p(__PRETTY_FUNCTION__); }

Dog& operator=(const Dog&) { p(__PRETTY_FUNCTION__); return *this; }
Dog& operator=(Dog &&) { p(__PRETTY_FUNCTION__); return *this; }
};

template<typename T>
struct MyList
{
void push_front(T &&v) {}
};

int main(int argc, char **argv)
{
MyList<Dog> ml;
std::forward_list<Dog> fl;
Dog dog;

p("ml.push_front");
ml.push_front(std::move(dog)); // lvalue reference
p("fl.push_front");
fl.push_front(std::move(dog)); // rvalue reference

return 0;
}

Alf P. Steinbach

unread,
Jan 25, 2023, 4:49:31 PMJan 25
to
On 2023-01-25 9:43 PM, Pawel Por wrote:
> Hello,
>
> Please help me to understand why in the following example MyList::push_front(T&&) receives argument by lvalue reference while std::forward_list::push_front(_Tp&&) receives argument by rvalue reference.

That is not the case.


> Thank you in advance
>
> #include <iostream>
> #include <forward_list>
>
> template <typename T>
> void p(const T &v)
> {
> std::cout << v << std::endl;
> }
>
> struct Dog
> {
> Dog() { p(__PRETTY_FUNCTION__); }
> Dog(const Dog&) { p(__PRETTY_FUNCTION__); }
> Dog(Dog&&) { p(__PRETTY_FUNCTION__); }
>
> Dog& operator=(const Dog&) { p(__PRETTY_FUNCTION__); return *this; }
> Dog& operator=(Dog &&) { p(__PRETTY_FUNCTION__); return *this; }
> };
>
> template<typename T>
> struct MyList
> {
> void push_front(T &&v) {}
> };
>
> int main(int argc, char **argv)
> {
> MyList<Dog> ml;
> std::forward_list<Dog> fl;
> Dog dog;
>
> p("ml.push_front");
> ml.push_front(std::move(dog)); // lvalue reference
> p("fl.push_front");
> fl.push_front(std::move(dog)); // rvalue reference
>
> return 0;
> }


You don't get any output from the call of `MyList::push_front` because
it doesn't do anything.

Here's more sane code:

#include <iostream>
#include <forward_list>
#include <utility>

template <typename T>
void p(const T &v)
{
std::cout << v << std::endl;
}

struct Dog
{
Dog() { p(FUNCSIG); }
Dog(const Dog&) { p(FUNCSIG); }
Dog(Dog&&) { p(FUNCSIG); }

Dog& operator=(const Dog&) { p(FUNCSIG); return *this; }
Dog& operator=(Dog &&) { p(FUNCSIG); return *this; }
};

template<typename T>
struct MyList
{
void push_front(T &&v) { T x = std::move( v ); (void) x; }
};

int main()
{
MyList<Dog> ml;
std::forward_list<Dog> fl;
Dog dog;

p("ml.push_front");
ml.push_front(std::move(dog));
p("fl.push_front");
fl.push_front(std::move(dog));
}

Output:

[C:\root\temp]
> cl _.cpp /D FUNCSIG=__FUNCSIG__
_.cpp

[C:\root\temp]
> _
__cdecl Dog::Dog(void)
ml.push_front
__cdecl Dog::Dog(struct Dog &&)
fl.push_front
__cdecl Dog::Dog(struct Dog &&)

[C:\root\temp]
> g++ _.cpp -D FUNCSIG=__PRETTY_FUNCTION__

[C:\root\temp]
> _
__cdecl Dog::Dog(void)
ml.push_front
__cdecl Dog::Dog(struct Dog &&)
fl.push_front
__cdecl Dog::Dog(struct Dog &&)

- Alf

Pawel Por

unread,
Jan 27, 2023, 3:28:39 PMJan 27
to
Thank you for an answer. Please note that the MyList<T>::push_front(T &&v) argument is still passed by lvalue reference. Only inside the body of its function the move semantics is in use.

void push_front(T &&v)
{
p("T x = std::move( v ); (void) x;");
T x = std::move( v ); (void) x;
}

Output:
ml.push_front
T x = std::move( v ); (void) x;
Dog::Dog(Dog&&)

Pawel Por

unread,
Jan 28, 2023, 4:53:04 AMJan 28
to
I think it is related to the concept of "universal references" coined by Scott Mayers
https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

Andrey Tarasevich

unread,
Jan 29, 2023, 9:33:46 PMJan 29
to
On 01/27/23 12:27 PM, Pawel Por wrote:
>
> Thank you for an answer. Please note that the MyList<T>::push_front(T &&v) argument is still passed by lvalue reference. Only inside the body of its function the move semantics is in use.
>

What are you talking about? Where did you get thet idea that "argument
is still passed by lvalue reference"? What behavior made you to draw
that conclusion?

> void push_front(T &&v)
> {
> p("T x = std::move( v ); (void) x;");
> T x = std::move( v ); (void) x;
> }
>
> Output:
> ml.push_front
> T x = std::move( v ); (void) x;
> Dog::Dog(Dog&&)

Aaaaand...? What are we expected to see in this output?

> I think it is related to the concept of "universal references" coined by Scott Mayers

No

--
Best regards,
Andrey

Pawel Por

unread,
Mar 5, 2023, 6:35:07 AMMar 5
to
> What behavior made you to draw that conclusion ?
I cannot see any Dog's constructor or operator to be called while calling MyList::push_front(). No __PRETTY_FUNCTION__ output (refer to the code below).

> What are we expected to see in this output ?
I expect the Dog::operator=(Dog&&) to be called

I'm aware that __PRETTY_FUNCTION__ shows the following but why the Dog::operator=(Dog&&) isn't called ?
void MyList<T>::push_front(T&&) [with T = Dog]

The updated code is as follows (I'm using g++ 9.4.0 Ubuntu 20.04)
#include <iostream>
#include <forward_list>
#include <utility>

template <typename T>
void p(const T &v)
{
std::cout << v << std::endl;
}

struct Dog
{
Dog() { p(__PRETTY_FUNCTION__); }
Dog(const Dog&) { p(__PRETTY_FUNCTION__); }
Dog(Dog&&) { p(__PRETTY_FUNCTION__); }

Dog& operator=(const Dog&) { p(__PRETTY_FUNCTION__); return *this; }
Dog& operator=(Dog &&) { p(__PRETTY_FUNCTION__); return *this; }
};

template<typename T>
struct MyList
{
void push_front(T &&v) { p(__PRETTY_FUNCTION__); T x = std::move( v ); (void) x; }

Pawel Por

unread,
Mar 5, 2023, 1:20:49 PMMar 5
to
> > What are we expected to see in this output ?
> I expect the Dog::operator=(Dog&&) to be called
I expect the Dog::Dog(Dog&&) to be called (sorry for confusion)

Andrey Tarasevich

unread,
Mar 5, 2023, 1:28:11 PMMar 5
to
On 03/05/23 3:34 AM, Pawel Por wrote:
>> What behavior made you to draw that conclusion ?
> I cannot see any Dog's constructor or operator to be called while calling MyList::push_front(). No __PRETTY_FUNCTION__ output (refer to the code below).

Um... What? But your code outputs

Dog::Dog(Dog&&)

from inside 'MyList::push_front()'. This is clearly a call to Dog's
constructor. Why do you say that you "cannot see any Dog's constructor
[...] to be called"?

And that still does not allow you to differentiate between lvalue
reference and rvalue reference. So, my question still stands.

>> What are we expected to see in this output ?
> I expect the Dog::operator=(Dog&&) to be called

Why? The code you provided does not use assignment operator at all. Why
would you expect assignment operator to be called?

> I'm aware that __PRETTY_FUNCTION__ shows the following but why the Dog::operator=(Dog&&) isn't called ?

Again, show me the exact location where you expect the assignment
operator to be called.

--
Best regards,
Andrey

Andrey Tarasevich

unread,
Mar 5, 2023, 1:30:44 PMMar 5
to
But it _is_ called. Here's the output I get from your code

Dog::Dog()
ml.push_front
void MyList<T>::push_front(T&&) [with T = Dog]
Dog::Dog(Dog&&)
fl.push_front
Dog::Dog(Dog&&)

--
Best regards,
Andrey

Pawel Por

unread,
Mar 8, 2023, 1:29:25 PMMar 8
to
Thank you for response.

> Why? The code you provided does not use assignment operator at all. Why
> would you expect assignment operator to be called?
Right, I expect the move constructor to be called - sorry for confusion.

>> I expect the Dog::Dog(Dog&&) to be called (sorry for confusion)
>But it _is_ called. Here's the output I get from your code
It is the std::move that forces the move constructor to be called. For me it looks like the argument is passed by lvalue reference to the MyList<T>::push_front(T&&) function.
I've modified the code to show this:

#include <iostream>
#include <forward_list>
#include <utility>

template <typename T>
void p(const T &v)
{
std::cout << v << std::endl;
}

struct Dog
{
Dog() { p(__PRETTY_FUNCTION__); }
Dog(const Dog&) { p(__PRETTY_FUNCTION__); }
Dog(Dog&&) { p(__PRETTY_FUNCTION__); }

Dog& operator=(const Dog&) { p(__PRETTY_FUNCTION__); return *this; }
Dog& operator=(Dog &&) { p(__PRETTY_FUNCTION__); return *this; }
};

template<typename T>
struct MyList
{
void push_front(T &&v) { p(__PRETTY_FUNCTION__); p("before std::move"); T x = std::move( v ); p("after std::move"); (void) x;}
};

int main()
{
MyList<Dog> ml;
std::forward_list<Dog> fl;
Dog dog;

p("ml.push_front");
ml.push_front(std::move(dog));
p("fl.push_front");
fl.push_front(std::move(dog));
}

Here is the output:
Dog::Dog()
ml.push_front
void MyList<T>::push_front(T&&) [with T = Dog]
before std::move
Dog::Dog(Dog&&)
after std::move
fl.push_front
Dog::Dog(Dog&&)

I expect the Dog's move constructor to be called when passing an argument to MyList<T>::push_front(T&&)

Andrey Tarasevich

unread,
Mar 8, 2023, 4:08:50 PMMar 8
to
On 03/08/23 10:29 AM, Pawel Por wrote:
>>> I expect the Dog::Dog(Dog&&) to be called (sorry for confusion)
>> But it _is_ called. Here's the output I get from your code
> It is the std::move that forces the move constructor to be called.

Precisely. Exactly as it should be.

A _named_ reference is always an lvalue. If you want to call a move
constructor on that reference, you have "unname" that reference. That's
exactly what `std::move` does.

> For me it looks like the argument is passed by lvalue reference to the MyList<T>::push_front(T&&) function.

I still don't understand what you are trying to say here. On the
receiving side (i.e. inside the function), there's no tangible
difference between an argument passed by lvalue reference and an
argument passed by rvalue reference. I don't understand how you managed
to differentiate between the two.

> I expect the Dog's move constructor to be called when passing an argument to MyList<T>::push_front(T&&)

This does not make any sense at all. Argument of `push_front` is a
direct-bound reference. Direct binding of a reference is just... binding
a reference, nothing more. It never calls any constructors. It never
does anything more than just binding of a reference.

--
Best regards,
Andrey

Andrey Tarasevich

unread,
Mar 8, 2023, 5:40:39 PMMar 8
to
On 03/08/23 1:08 PM, Andrey Tarasevich wrote:
> On 03/08/23 10:29 AM, Pawel Por wrote:
>>>> I expect the Dog::Dog(Dog&&) to be called (sorry for confusion)
>>> But it _is_ called. Here's the output I get from your code
>> It is the std::move that forces the move constructor to be called.
>
> Precisely. Exactly as it should be.
>
> A _named_ reference is always an lvalue. If you want to call a move
> constructor on that reference, you have "unname" that reference. That's
> exactly what `std::move` does.
>

To clarify this a bit:

`std::move` does not call any constructors. And `std::move` does not
"force" any constructors to be called. All it does is it "unnames" a
reference, i.e. it turns an lvalue into an xvalue.

Everything else is a completely separate consequence of that, which
happens later, _after_ `std::move`. Overload resolution sees an xvalue
argument and chooses move constructor for this initialization.

--
Best regards,
Andrey

Pawel Por

unread,
Mar 10, 2023, 5:19:18 PMMar 10
to
>`std::move` does not call any constructors. And `std::move` does not
>"force" any constructors to be called. All it does is it "unnames" a
>reference, i.e. it turns an lvalue into an xvalue.

>Everything else is a completely separate consequence of that, which
>happens later, _after_ `std::move`. Overload resolution sees an xvalue
>argument and chooses move constructor for this initialization.

I think this is what I do here, "unname" a reference via std::move. Am I correct ?
ml.push_front(std::move(dog));

That's why I expect the move constructor to be called while passing an argument to MyList<T>::push_front(T&&).

Andrey Tarasevich

unread,
Mar 10, 2023, 6:48:31 PMMar 10
to
On 03/10/23 2:19 PM, Pawel Por wrote:
>> `std::move` does not call any constructors. And `std::move` does not
>> "force" any constructors to be called. All it does is it "unnames" a
>> reference, i.e. it turns an lvalue into an xvalue.
>
>> Everything else is a completely separate consequence of that, which
>> happens later, _after_ `std::move`. Overload resolution sees an xvalue
>> argument and chooses move constructor for this initialization.
>
> I think this is what I do here, "unname" a reference via std::move. Am I correct ?
> ml.push_front(std::move(dog));

Yeah, except that `dog` here is not a reference but rather a full-blown
object. But the effect is pretty much the same: that `std::move`
"unnames" an lvalue, turns it into an xvalue.

> That's why I expect the move constructor to be called while passing an argument to MyList<T>::push_front(T&&).

No, your expectations are completely unjustified. The constructor will
not be called here.

Again: passing by reference never calls any constructors, unless there's
a conversion involved. And in your example there's no conversion.

--
Best regards,
Andrey

Reply all
Reply to author
Forward
0 new messages