JobNick
JobNick

Reputation: 503

std::function move constructor not called

Consider this code:

void makeNew(std::function<void()>&& func)
{
    std::function<void()> p = func;
}

void test(){}

usage:

makeNew(std::bind(test));

Expected result:

Temporary std::function<void()> object created from result type of std::bind(test) then this temporary std::function object passed as rvalue reference to makeNew and then move constructor called when std::function p = func; is invoked.

What actually happens (using VS2010 and stepping with debugger):

For the following statement std::function<void()> p = func;, copy constructor is invoked instead of move constructor.

Can someone explain this behavior ?

Upvotes: 0

Views: 581

Answers (2)

Steve Jessop
Steve Jessop

Reputation: 279225

An rvalue reference is not an rvalue (other than when it's returned from a function). It's just a distinct type of lvalue with different binding rules from normal references.

If you want to move from an rvalue reference, use std::move, same as any other reference. std::move returns an rvalue reference type, therefore the expression std::move(func) is an xvalue, which therefore is an rvalue and can be moved from. func is an lvalue even though it has rvalue-reference type.

Note that it's pretty rare to need a function that takes an rvalue reference. You'd do it to exclude lvalues, either because you want to prevent them being passed in at all or because you have an lvalue overload that does something different. The most common case is that you want your function to move from an rvalue or copy an lvalue. So you can instead take the parameter by value and move from it (as in Joseph's answer), and there's little or no overhead compared with going to the trouble of writing two different functions.

Upvotes: 4

Joseph Mansfield
Joseph Mansfield

Reputation: 110658

You are using rvalue references incorrectly. Taking an argument by rvalue reference is not a very common thing to want to do. It does not say "I want to move from the argument" and it does not say "I want to later move from this parameter". Instead, it's a reference - nothing is copied or moved, it's just referenced. Inside the function, the expression func is an lvalue (because things that are named are lvalues), so the copy constructor will be called when initialising p with it.

What you should be doing instead is taking the argument by value:

void makeNew(std::function<void()> p)
{
  // ...
}

If you pass an rvalue to this function, it will be moved into the function. If you pass an lvalue, it will be copied. And look, now there's no need to have an extra std::function inside makeNew. Just use the argument itself! If you really need to move from the argument for some reason, then you could do:

void makeNew(std::function<void()> func)
{
  std::function<void()> p = std::move(func);
}

Using std::move turns the expression func (which is an lvalue) into an rvalue expression. Then it will be moved from. However, I can't see any good reason to need to do this unless you're moving into yet another function.

Of course, on the off chance that I'm completely wrong and you really do want to take an rvalue reference, if you want to move from it, you still need to do std::move.

Upvotes: 3

Related Questions