6

In the lecture about universal references, Scott Meyers (at approximately 40th minute) said that objects that are universal references should be converted into real type, before used. In other words, whenever there is a template function with universal reference type, std::forward should be used before operators and expressions are used, otherwise a copy of the object might be made.

My understanding of this is in the following example :

#include <iostream>

struct A
{
  A() { std::cout<<"constr"<<std::endl; }
  A(const A&) { std::cout<<"copy constr"<<std::endl; }
  A(A&&) { std::cout<<"move constr"<<std::endl; }
  A& operator=(const A&) { std::cout<<"copy assign"<<std::endl; return *this; }
  A& operator=(A&&) { std::cout<<"move assign"<<std::endl; return *this; }

  ~A() { std::cout<<"destr"<<std::endl; }

  void bar()
  {
    std::cout<<"bar"<<std::endl;
  }
};

A getA()
{
  A a;
  return a;
}

template< typename T >
void callBar( T && a )
{
  std::forward< T >( a ).bar();
}

int main()
{
  {
    std::cout<<"\n1"<<std::endl;
    A a;
    callBar( a );
  }

  {
    std::cout<<"\n2"<<std::endl;
    callBar( getA() );
  }
}

As expected, the output is :

1
constr
bar
destr

2
constr
move constr
destr
bar
destr

The question really is why is this needed?

std::forward< T >( a ).bar();

I tried without std::forward, and it seems to work fine (the output is the same).

Similarly, why he recommends to use move inside the function with rvalue? (the answer is the same as for std::forward)

void callBar( A && a )
{
  std::move(a).bar();
}

I understand that both std::move and std::forward are just casts to appropriate types, but are these casts really needed in the above example?

Bonus : how can the example be modified to produce the copy of the object that is passed to that function?

BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 2
    You beat me by not much asking the same question. I'm also looking for explanation on this. – Vikas Oct 11 '12 at 10:22
  • @Vikas So, does it mean that my understanding was correct? You do need to use move in functions using rvalue? – BЈовић Oct 11 '12 at 10:23
  • What I meant that I also have similar doubts after watching his talk. And I was also going to ask this question here :-). – Vikas Oct 11 '12 at 10:27
  • [What is "rvalue reference for *this"?](http://stackoverflow.com/q/8610571/500104) :) – Xeo Oct 11 '12 at 15:54
  • What's the point in moving in callBar you're not going to take a copy? – Kaz Dragon Oct 16 '12 at 14:22
  • @KazDragon Take a look into my [answer](http://stackoverflow.com/a/12854624/476681). `std::move` is just a cast. If you do not do it, you are going to use the parameter `a`, which is a lvalue reference. `std::move` turns it into rvalue reference. In my answer you can see a modifications to the original example, which explains what is going on. With these modifications, it should be clear – BЈовић Oct 17 '12 at 06:22
  • @BЈовић Socratic method. Move is an optimisation for a copy. I was suggesting that the reason might become clear if there were actually copies at the end of the call chain. – Kaz Dragon Oct 17 '12 at 09:06
  • @KazDragon `void callBar( A && a ) { A ca( a ); ca.bar(); }` Like this? – BЈовић Oct 17 '12 at 10:34

4 Answers4

2

It's needed because bar() might be overloaded separately for rvalues and lvalues. That means that it might do something differently, or flat out not be allowed, depending on if you correctly described a as an lvalue or an rvalue, or just blindly treated it like an lvalue. Right now, most users don't use this functionality and don't have exposure to it because the most popular compilers don't support it - even GCC 4.8 doesn't support rvalue *this. But it is Standard.

Scrubbins
  • 150
  • 4
  • So, in that case, this should be ok as well : `void callBar( A && a ) { std::move(a).bar();std::move(a).bar(); }`, and as explained in the video, the parameter should be casted before being accessed. Right? – BЈовић Oct 11 '12 at 11:45
  • Depends. Are you ok with `bar()` potentially stealing `a`'s resources? Because it might be implemented that way – Scrubbins Oct 12 '12 at 08:05
2

There are two different uses for && on a parameter to a function. For an ordinary function it means that the argument is an rvalue reference; for a template function it means that it can be either an rvalue reference or an lvalue reference:

template <class T> void f(T&&); // rvalue or lvalue
void g(T&&);                    // rvalue only
void g(T&)                      // lvalue only

void h() {
    C c;
    f(c);            // okay: calls f(T&)
    f(std::move(c)); // okay: calls f(T&&)
    g(c);            // error: c is not an rvalue
    g(std::move(c)); // okay: move turns c into an rvalue
}

Inside f and g, applying std::forward to such an argument preserves the lvalue- or rvalue-ness of the argument, so in general that's the safest way to forward an argument to another function.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • `f(c); // error: c is not an rvalue ` Why this fails? Shouldn't it accept it, since T&& is the universal reference (as explained in the lecture)? – BЈовић Oct 11 '12 at 17:11
  • @BЈовић - no, outside of a template, `&&` means rvalue reference. It's only in a template that it means rvalue reference or lvalue reference. – Pete Becker Oct 11 '12 at 17:26
  • @PeteBecker but f() *is* a template, so `f(c)` is correct. Also, I'd change the signature of `g` to `g(C&&)` and `g(C&)` to make the code and comments match. (in the present form `g(c)` is an error) – mitchnull Oct 12 '12 at 07:44
  • @mitchnull - whoops, I got the comments backwards. I'll fix it. Thanks. – Pete Becker Oct 12 '12 at 12:21
1
void callBar( A && a )
{
  std::move(a).bar();
}

In the case where you have an rvalue reference as a parameter, which can only bind to an rvalue you normally want to use move semantics to move from this this rvalue and take it's guts out.

The parameter itself is an lvalue, because it is a named thing. You can take it's address.

So in order to make it an rvalue again and be able to move from it, you apply std::move to it. If you were literally just calling a function on a passed parameter, I don't see why you'd have a parameter that is an rvalue reference.

You only want to pass an rvalue reference if you are going to move from this inside your function, which is why you then have to use std::move.

Your example here doesn't actually make much sense in that respect.

Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
  • The decision to move should be made at the call site. A function should not decide to move an argument that it got by reference: it can't know that moving is safe. – Pete Becker Oct 11 '12 at 10:45
  • 1
    @PeteBecker What's the point of rvalue references in parameter lists if not for passing arguments that are safe to move from? – Luc Danton Oct 11 '12 at 11:06
  • @LucDanton - `&&` on an argument whose type is a template parameter doesn't tell you that it's an rvalue; that's the point of the "universal reference" term. `template void f(C&&); void h() { C c; f(c); }` Here, `f` gets called with an lvalue. `void g(C&&); void h() { C c; f(c); }` Here, the call to `f` is an error, because `c` is an lvalue and `g` expects an rvalue. – Pete Becker Oct 11 '12 at 11:16
  • Ah seemed I missed the fact that `callBar` is indeed an induced context and therefore an universial reference. Hmmmm – Tony The Lion Oct 11 '12 at 11:20
  • @PeteBecker While I haven't seen the talk, I've taken the second `callBar` (in the OP) to not be a template. 'Similarly' introducing a parallel between universal reference+`std::forward` and rvalue reference+`std::move`. – Luc Danton Oct 11 '12 at 11:26
  • @LucDanton - could be, although it **really** should have a different name if it's purpose is to show something different. – Pete Becker Oct 11 '12 at 11:28
  • @PeteBecker So, what you are saying is that the move should not be called in a function taking a rvalue reference? (forward for universal reference) – BЈовић Oct 11 '12 at 12:33
  • @BЈовић - no, it's okay if it's an rvalue reference, and that's what you have in a normal function that takes its argument as a `&&`. In a **template** a parameter that comes in as a `&&` can be either an lvalue reference or an rvalue reference, and in that case, the code shouldn't call `move`. – Pete Becker Oct 11 '12 at 13:41
  • @PeteBecker The comment above could have been an answer :P – BЈовић Oct 11 '12 at 15:20
0

What is said in the lecture is this :

void doWork( Widget&& param )
{
  ops and exprs using std::move(param)
}

SM: What this means is : if you see code that takes a rvalue reference, and you see use of that parameter without being wrapped by move, it is highly suspect.

After some thought, I realized that it is correct (as expected). Changing the callBar function in the original example to this demonstrate the point :

void reallyCallBar( A& la )
{
  std::cout<<"lvalue"<<std::endl;
  la.bar();
}

void reallyCallBar( A&& ra )
{
  std::cout<<"rvalue"<<std::endl;
  ra.bar();
}

template< typename T >
void callBar( T && a )
{
  reallyCallBar( std::forward< T >( a ) );
}

If the std::forward wasn't used in callBar, then the reallyCallBar( A& ) would be used. Because a in callBar is a lvalue reference. std::forward makes it a rvalue, when the universal reference is the rvalue reference.

Next modification proves the point even further :

void reallyCallBar( A& la )
{
  std::cout<<"lvalue"<<std::endl;
  la.bar();
}

void reallyCallBar( A&& ra )
{
  std::cout<<"rvalue"<<std::endl;
  reallyCallBar( ra );
}

template< typename T >
void callBar( T && a )
{
  reallyCallBar( std::forward< T >( a ) );
}

Since std::move is not used in the reallyCallBar( A&& ra ) function, it doesn't enter the endless loop. Instead it calls the version taking lvalue reference.

Therefore (as explained in the lecture) :

  • std::forward must be used on universal references
  • std::move must be used on rvalue references
BЈовић
  • 62,405
  • 41
  • 173
  • 273