Passing arguments to std::async by reference fails

I've noticed that it's impossible to pass a non-const reference as an argument to std::async.

#include <functional>
#include <future>

void foo(int& value) {}

int main() {
    int value = 23;
    std::async(foo, value);
}

My compiler (GCC 4.8.1) gives the following error for this example:

error: no type named ‘type’ in ‘class std::result_of<void (*(int))(int&)>’

But if I wrap the value passed to std::async in std::reference_wrapper, everything is OK. I assume this is because std::async takes it's arguments by value, but I still don't understand the reason for the error.


It's a deliberate design choice/trade-off.

First, it's not necessarily possible to find out whether the functionoid passed to async takes its arguments by reference or not. (If it's not a simple function but a function object, it could have an overloaded function call operator, for example.) So async cannot say, "Hey, let me just check what the target function wants, and I'll do the right thing."

So the design question is, does it take all arguments by reference if possible (i.e. if they're lvalues), or does it always make copies? Making copies is the safe choice here: a copy cannot become dangling, and a copy cannot exhibit race conditions (unless it's really weird). So that's the choice that was made: all arguments are copied by default.

But then, the mechanism is written so that it actually fails to then pass the arguments to a non-const lvalue reference parameter. That's another choice for safety: otherwise, the function that you would expect to modify your original lvalue instead modifies the copy, leading to bugs that are very hard to track down.

But what if you really, really want the non-const lvalue reference parameter? What if you promise to watch out for dangling references and race conditions? That's what std::ref is for. It's an explicit opt-in to the dangerous reference semantics. It's your way of saying, "I know what I'm doing here."


std::async (and other functions that do perfect forwarding) look at the type of the argument that you pass to figure out what to do. They do not look at how that argument will eventually be used. So, to pass an object by reference you need to tell std::async that you're using a reference. However, simply passing a reference won't do that. You have to use std::ref(value) to pass value by reference.


The issue itself is only marginally related to std::async(): When defining the result of the operation, std::async() uses std::result_of<...>::type with all its arguments being std::decay<...>::type'ed. This is reasonable because std::async() takes arbitrary types and forwards them to store them in some location. To store them, values are needed for the function object as well as for the arguments. Thus, std::result_of<...> is used similar to this:

typedef std::result_of<void (*(int))(int&)>::type result_type;

... and since int can't be bound to an int& (int isn't an lvalue type was is needed to be bound to int&), this fails. Failure in this case means that std::result_of<...> doesn't define a nested type.

A follow-up question could be: What is this type used to instantiate std::result_of<...>? The idea is that the function call syntax consisting of ResultType(ArgumentTypes...) is abused: instead of a result type, a function type is passed and std::result_of<...> determines the type of the function called when that function type is called with the given list of arguments is called. For function pointer types it isn't really that interesting but the function type can also be a function object where overloading needs to be taken into account. So basically, std::result_of<...> is used like this:

typedef void (*function_type)(int&);
typedef std::result_of<function_type(int)>::type result_type; // fails
typedef std::result_of<function_type(std::reference_wrapper<int>)>::type result_type; //OK