1
#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?

PowerGamer
  • 2,106
  • 2
  • 17
  • 33
  • 3
    Please edit your question to include the code indicated by (**) and the _exact_ compilation error you get. – Captain Obvlious Jan 20 '15 at 14:25
  • Can you find out what 'Callable' is in the first call to async_g? e.g. by using `__PRETTY_FUNCTION__` inside it, to find out the type of fn. I think the answer to (1) lies therein. – greggo Jan 20 '15 at 14:27
  • 2
    1 and 2: read about universal references. – ForEveR Jan 20 '15 at 14:38
  • @CaptainObvlious I didn't include the (single) error message I get in MSVS2013 because it is 182 lines long. – PowerGamer Jan 20 '15 at 14:45
  • @greggo Thanks for the hint, apparently in both calls `async_g` template becomes `std::future async_g&> (std::function&)`. – PowerGamer Jan 20 '15 at 14:54

1 Answers1

5

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.

  1. 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.

  2. 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.

  3. 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.

jepio
  • 2,276
  • 1
  • 11
  • 16
  • Just to make sure I understood the theory you explained correctly: if I change your lambda to `[&]{g(fn);}` will I have to worry about lifetime of `bound_f1`, `bound_f2` and why? – PowerGamer Jan 20 '15 at 16:23
  • I will say yes you will have to worry about lifetime, but I'm only about 97% sure. If `fn` was bound to a temporary, then that temporary dies when we leave `async_g`, which would mean the lambda has a reference to a variable that doesn't exist anymore. If `fn` was bound to an lvalue, then it is entirely possible that you will pass the returned future between threads and the thread that spawned the future terminates taking `fn` with it. So be very careful about passing by reference in a parallel environment. – jepio Jan 20 '15 at 16:31
  • Your solution does not work for `async_g(f1);`. How can I modify `async_g` and `g` to make them work with everything callable like regular functions, `std::bind`, lambdas and structs with overloaded opeartor() ? – PowerGamer Jan 20 '15 at 17:07
  • Why does `async` not move its value arguments (aka forward) into the called function? I cannot think of a good reason: `async` stores a copy of some arguments, and after making the call it never uses them again, and nobody else should have a pointer into them? – Yakk - Adam Nevraumont Jan 20 '15 at 18:31
  • Yakk: I think all `async` arguments are stored as values (not references) so there's no easy way to `forward` them (all of them would end up as rvalues). – jepio Jan 20 '15 at 19:39
  • PowerGamer: This compiles with clang 3.4 ,but fails with gcc 4.8.2 as well. I don't know how to solve your problem, although as it stands right now it should work with everything except for regular functions. – jepio Jan 20 '15 at 19:41
  • For some reason gcc 6.1 and clang 3.8 still disagree on the validity of the `async_g(f1);` call. Fortunately there is a workaround: `async_g(&f1);` works with both compilers. – vedg Aug 20 '16 at 19:36