vdaras
vdaras

Reputation: 352

Why does this need an explicit std::move?

Let's say I got a Foo class containing an std::vector constructed from std::unique_ptr objects of another class, Bar.

typedef std::unique_ptr<Bar> UniqueBar;

class Foo {
    std::vector<UniqueBar> bars;
public:
    void AddBar(UniqueBar&& bar);
};

void Foo::AddBar(UniqueBar&& bar) {
    bars.push_back(bar);
}

This one results in a compilation error (in g++ 4.8.1) saying that the the copy constructor of std::unique_ptr is deleted, which is reasonable. The question here is, since the bar argument is already an rvalue reference, why does the copy constructor of std::unique_ptr is called instead of its move constructor?

If I explicitly call std::move in Foo::AddBar then the compilation issue goes away but I don't get why this is needed. I think it's quite redundant.

So, what am I missing?

Upvotes: 16

Views: 1331

Answers (4)

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153800

Basically, every object which has a name is an lvalue. When you pass an object to a function using an rvalue reference the function actually sees an lvalue: it is named. What the rvalue reference does, however, indicate is that it came from an object which is ready to be transferred.

Put differently, rvalue references are assymmetrical:

  • they can only receive rvalues, i.e., either temporary objects, objects about to go away, or objects which look as if they are rvalues (e.g., the result of std::move(o))
  • the rvalue reference itself looks, however, like an lvalue

Upvotes: 18

David G
David G

Reputation: 96790

bar is defined as an rvalue-reference, but its value-category is an lvalue. This is so because the object has a name. If it has a name, it's an lvalue. Therefore an explicit std::move is necessary because the intention is to get rid of the name and return an xvalue (eXpiring-rvalue).

Upvotes: 3

Confusing as it might seem, an rvalue-reference binds to an rvalue, but used as an expression is an lvalue.

Upvotes: 9

juanchopanza
juanchopanza

Reputation: 227370

bar is actually an lvalue, so you need to pass it through std::move, so that it is seen as an rvalue in the call to push_back.

The Foo::AddBar(UniqueBar&& bar) overload simply ensures that this overload is picked when an rvalue is passed in a call to Foo::AddBar. But the bar argument itself has a name and is an lvalue.

Upvotes: 8

Related Questions