Perfect forwarding

Hi
My question is why does the following code always end up invoking the copy constructor of MyClass ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>

class MyClass
{
  public:
    MyClass()
    {
      std::cout << "constr\n";
    }

    MyClass(const MyClass& rhs)
    {
      std::cout << "copy constr\n";
    }

    MyClass(MyClass&& rhs)
    {
      std::cout << "move constr\n";
    }
};

template <typename T>
void doCopy(T&& arg) //MyClass&& && should collapse to MyClass&& 
{
  MyClass copy(arg); //Yet it calls the copy constructor ?
}


int main()
{
  MyClass a;

  doCopy(std::move(a)); //std::move() returns rval reference (MyClass&&)

  return 0;
}


To my understanding std::move() returns an rvalue reference and so T&& will be an rvalue reference to an rvalue reference, which should collapse into just an rvalue reference (according to google) which should thus invoke the move constructor.

(and when an lvalue reference is passed then T should collapse into an lvalue reference and invoke the copy constructor accordingly)

But for some reason its always invoking the copy constructor regardless of whether im passing an lvalue reference or an rvalue reference :/
Last edited on
MyClass&& && should collapse to MyClass&&
No. C++ does pattern matching to resolve template parameters. T == MyClass. 'MyClass&& &&' is nonsense.

Rvalue references behave as lvalue references if directly passed to a function or assigned to an object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <typename T>
void doCopy1(T&& arg){
    MyClass copy(arg); // Normal copy.
}

template <typename T>
void doCopy2(T&& arg){
    MyClass copy(std::move(arg)); // Move construction.
}

template <typename T>
void copyUnique1(std::unique_ptr<T> &&p){
    std::unique_ptr<T> p2 = p; // Error. Unique pointers can't be copied.
}

template <typename T>
void copyUnique1(std::unique_ptr<T> &&p){
    std::unique_ptr<T> p2 = std::move(p); // OK. Unique pointer correctly moved.
}
Type and value category are independent. Your function parameter (when passed an rvalue, maybe cv-qualified) has the type "rvalue reference to cv T", but it is an lvalue.)
The compiler only automatically moves from rvalues.

This is a pretty common pitfall. See this post:
http://www.cplusplus.com/forum/general/219122/

The rule of thumb is this:
If the object has a name, its value category is lvalue.
If a function accepts a rvalue reference, then it should almost always call move() on that reference.
If a function accepts a forwarding reference, then it should almost always call forward() on that reference.


The latter guideline (use forward()) applies here, because of a special rule about the type T&& for deduced T.

This is where ref-collapsing applies: if you pass doCopy() an lvalue (ignoring cv-qualifiers), the deduced type is T&& & --> T&; which can't be moved from safely. You can safely move if and only if the argument is actually an rvalue, in which case you get the T&& && --> T&& like you mentioned.

Go read Thomas Becker's famous article, here:
http://thbecker.net/articles/rvalue_references/section_01.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// only binds rvalues
void move_me(A&& arg) 
{
  // arg's value category is lvalue; arg has type rvalue reference to A 
  // We know that the argument can be moved from, because the parameter is bound to an rvalue
  // To actually move from it,
  use_me(std::move(arg));
}

// binds every value category; the type of arg is not necessarily "rvalue-reference to T".
template <typename T>
void forward_me(T&& arg)
{ 
  // the value category of arg is lvalue; the type of arg is deduced
  // we wish to move arg if and only if arg is bound to an rvalue, otherwise copy
  foo(std::forward<T>(arg));
}
Last edited on
Topic archived. No new replies allowed.