While working on this question, I noticed that GCC (v4.7)'s implementation of std::function
moves its arguments when they are taken by value. The following code shows this behavior:
#include <functional>
#include <iostream>
struct CopyableMovable
{
CopyableMovable() { std::cout << "default" << '\n'; }
CopyableMovable(CopyableMovable const &) { std::cout << "copy" << '\n'; }
CopyableMovable(CopyableMovable &&) { std::cout << "move" << '\n'; }
};
void foo(CopyableMovable cm)
{ }
int main()
{
typedef std::function<void(CopyableMovable)> byValue;
byValue fooByValue = foo;
CopyableMovable cm;
fooByValue(cm);
}
// outputs: default copy move move
We see here that a copy of cm
is performed (which seems reasonable since the byValue
's parameter is taken by value), but then there are two moves. Since function
is operating on a copy of cm
, the fact that it moves its argument can be seen as an unimportant implementation detail. However, this behavior causes some trouble when using function
together with bind
:
#include <functional>
#include <iostream>
struct MoveTracker
{
bool hasBeenMovedFrom;
MoveTracker()
: hasBeenMovedFrom(false)
{}
MoveTracker(MoveTracker const &)
: hasBeenMovedFrom(false)
{}
MoveTracker(MoveTracker && other)
: hasBeenMovedFrom(false)
{
if (other.hasBeenMovedFrom)
{
std::cout << "already moved!" << '\n';
}
else
{
other.hasBeenMovedFrom = true;
}
}
};
void foo(MoveTracker, MoveTracker) {}
int main()
{
using namespace std::placeholders;
std::function<void(MoveTracker)> func = std::bind(foo, _1, _1);
MoveTracker obj;
func(obj); // prints "already moved!"
}
Is this behavior allowed by the standard? Is std::function
allowed to move its arguments? And if so, is it normal that we can convert the wrapper returned by bind
into a std::function
with by-value parameters, even though this triggers unexpected behavior when dealing with multiple occurrences of placeholders?
std::move. std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.
std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.
std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.
Instances of std::function can store, copy, and invoke any CopyConstructible Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.
std::function
is specified to pass the supplied arguments to the wrapped function with std::forward
. e.g. for std::function<void(MoveTracker)>
, the function call operator is equivalent to
void operator(CopyableMovable a)
{
f(std::forward<CopyableMovable>(a));
}
Since std::forward<T>
is equivalent to std::move
when T
is not a reference type, this accounts for one of the moves in your first example. It's possible that the second comes from having to go through the indirection layers inside std::function
.
This then also accounts for the problem you are encountering with using std::bind
as the wrapped function: std::bind
is also specified to forward its parameters, and in this case it is being passed an rvalue reference resulting from the std::forward
call inside std::function
. The function call operator of your bind expression is thus forwarding an rvalue reference to each of the arguments. Unfortunately, since you've reused the placeholder, it's an rvalue reference to the same object in both cases, so for movable types whichever is constructed first will move the value, and the second parameter will get an empty shell.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With