Reputation: 2136
#include <functional>
#include <future>
void z(int&&){}
void f1(int){}
void f2(int, double){}
template<typename Callable>
void g(Callable&& fn)
{
fn(123);
}
template<typename Callable>
std::future<void> async_g(Callable&& fn)
{
return std::async(std::launch::async, std::bind(&g<Callable>, fn));
}
int main()
{
int a = 1; z(std::move(a)); // Does not work without std::move, OK.
std::function<void(int)> bound_f1 = f1;
auto fut = async_g(bound_f1); // (*) Works without std::move, how so?
// Do I have to ensure bound_f1 lives until thread created by async_g() terminates?
fut.get();
std::function<void(int)> bound_f2 = std::bind(f2, std::placeholders::_1, 1.0);
auto fut2 = async_g(bound_f2);
// Do I have to ensure bound_f2 lives until thread created by async_g() terminates?
fut2.get();
// I don't want to worry about bound_f1 lifetime,
// but uncommenting the line below causes compilation error, why?
//async_g(std::function<void(int)>(f1)).get(); // (**)
}
Question1. Why the call at (*) works without std::move
?
Question2. Because I don't understand how code at (*) works the second question arises. Do I have to ensure each of the variables bound_f1
and bound_f2
live until the corresponding thread created by async_g() terminates?
Question3. Why does uncommenting the line marked by (**) cause compilation error?
Upvotes: 1
Views: 696
Reputation: 2281
Short answer: In the context of template type deduction, where the type is being deduced from an expression of the form
template <typename T>
T&& t
t is not an rvalue reference but a forwarding reference (keyword to look up, sometimes also called universal reference). This also happens for auto type deduction
auto&& t = xxx;
What forwarding references do is they bind to both lvalue and rvalue references, and are only really meant to be used with std::forward<T>(t)
to forward the parameter with the same reference qualifiers to the next function.
When you use this universal reference with an lvalue, the type deduced for T
is type&
, whereas when you use it with an rvalue reference the type will just be type
(comes down to reference collapsing rules). So now let's see what happens in your questions.
Your async_g
function is called with bound_f1
which is an lvalue. The type deduced for Callable
is therefore std::function<void(int)>&
and since you explicitly pass this type to g
, g
expects a parameter of lvalue type. When you call bind
it copies the arguments it binds to, so fn
will be copied, and this copy will then be passed to g
.
bind
(and thread/async) perform copies/moves of the arguments and if you think about it, this is the right thing to do. That way you do not have to worry about the lifetime of bound_f1/bound_f2
.
Since you actually passed an rvalue into the call to async_g
, this time the type deduced for Callable
is simply std::function<void(int)>
. But because you forwarded this type to g
, it expects an rvalue argument. While the type of fn
is rvalue, it itself is an lvalue and is copied into bind. So when the bound function executes, it tries to call
void g(std::function<void(int)>&& fn)
with an argument that is not an rvalue. And that's where your error comes from. In VS13 the final error message is:
Error 1 error C2664: 'void (Callable &&)' :
cannot convert argument 1 from 'std::function<void (int)>' to 'std::function<void (int)> &&'
c:\program files\microsoft visual studio 12.0\vc\include\functional 1149
Now you should actually rethink what you're trying to achieve with using forwarding references (Callable&&
), how far you need to forward and where the arguments are supposed to end up. This requires thinking about the lifetime of the arguments as well.
To overcome the error, it is enough to replace bind
with a lambda (always a good idea!). The code becomes:
template<typename Callable>
std::future<void> async_g(Callable&& fn)
{
return std::async(std::launch::async, [fn] { g(fn); });
}
This is the solution that requires the least effort, but the argument is copied into the lambda.
Upvotes: 5