35

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.

lizarisk
  • 7,562
  • 10
  • 46
  • 70
  • 5
    No need to type out `std::reference_wrapper`. Use `std::ref`. – chris Aug 21 '13 at 14:15
  • I do use `std::ref`, but thanks for a hint :) – lizarisk Aug 21 '13 at 14:19
  • You'll have to dig in the source code of libstdc++ if you're interested in the reason for this error: http://gcc.gnu.org/onlinedocs/gcc-4.8.1/libstdc++/api/a01256_source.html The line numbers to look at are in the errors. Good luck. – jrok Aug 21 '13 at 14:31
  • When using libc++ a similar error emerges, i.e., it seems to be intentional. I couldn't find the clause in the standard mandating this behavior, however. Also, note that just using `std::ref()` doesn't _solve_ the problem: it creates a different one! You now need to make sure that the reference variable stays around long enough. – Dietmar Kühl Aug 21 '13 at 14:34

3 Answers3

51

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

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
26

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.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
20

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
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380