What's the difference between passing a function directly to std::async and using std::bind?

Zoe*_*Zoe 5 c++ stdasync

I recently started adding async support to a library I'm working on, but I hit a slight problem. I started off with something like this (full context later):

return executeRequest<int>(false, d, &callback, false);
Run Code Online (Sandbox Code Playgroud)

That was before adding async support. I attempted to change it to:

return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
Run Code Online (Sandbox Code Playgroud)

But it failed to compile.

MCVE:

return executeRequest<int>(false, d, &callback, false);
Run Code Online (Sandbox Code Playgroud)

And it fails with:

In file included from main.cpp:2:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:38:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/functional:1505:56: error: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>'
      typedef typename result_of<_Callable(_Args...)>::type result_type;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:1709:49: note: in instantiation of template class 'std::_Bind_simple<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>' requested here
          __state = __future_base::_S_make_async_state(std::__bind_simple(
                                                       ^
main.cpp:33:25: note: in instantiation of function template specialization 'std::async<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool), X *, bool, RequestData &, int (*)(const int &), bool>' requested here
            return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
                        ^
In file included from main.cpp:2:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:38:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/functional:1525:50: error: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>'
        typename result_of<_Callable(_Args...)>::type
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
2 errors generated.
Run Code Online (Sandbox Code Playgroud)

Live example.

The only actual difference between the two (at least that I can see visibly) is that I need to explicitly pass this, because I'm referencing a member function

I played a little around with it, and managed to find that if I replace it with a const RequestData&, it's suddenly allowed. But it instead results in issues elsewhere, because the getter isn't const. At least from what I could find, I need to make it a const function, which is fine for the getter itself, but I also have some setters meaning I can't go with that.

Anyway, I figured I could try std::bind instead. I replaced the async call with:

auto func = std::bind(&X::executeRequest<int>, this, false, d, &callback, false);
return std::async(std::launch::async, func);
Run Code Online (Sandbox Code Playgroud)

And, for some reason, it worked.

The thing that confuses me here, is that it uses the same arguments both times (all three times if you count the non-async variant), and takes the this argument into consideration, given the function I'm calling is a member function.

I dug deeper, and found some alternative solutions (referencing std::thread though), that used std::ref. I know std::async runs std::thread under the hood, so I dug up the documentation:

The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g. with std::ref or std::cref). (emphasis mine)

That makes sense, and explains why it failed. I assume std::async is limited by this as well, and explains why it failed.

However, digging up std::bind:

The arguments to bind are copied or moved, and are never passed by reference unless wrapped in std::ref or std::cref. (emphasis mine)

I don't use std::ref (or if I replace with a const, std::cref) in either, but at least if I understood the documentation right, both of these should fail to compile. The example on cppreference.com also compiles without std::cref (tested in Coliru with Clang and C++ 17).

What's going on here?

If it matters, aside the coliru environment, I originally reproduced the issue in Docker, running Ubuntu 18.04 with Clang 8.0.1 (64 bit). Compiled against C++ 17 in both cases.

Hol*_*olt 5

该标准略有不同。对于std::bind

要求:is_­constructible_­v<FD, F>应为true。对于每个Tiin BoundArgsis_­constructible_­v<TDi, Ti>应该是trueINVOKE(fd, w1, w2, …, wN)([func.require])应当对某些值的有效表达w1w2,...,,wN其中N具有值sizeof...(bound_­args)g如下所述,呼叫包装器的cv限定词cv 不应是volatile或const volatile。

返回:参数转发调用包装器g([func.require])。的效果g(u1, u2, …, uM)应为

INVOKE(fd, std::forward<V1>(v1), std::forward<V2>(v2), …, std::forward<VN>(vN))
Run Code Online (Sandbox Code Playgroud)

其中v1,... vN具有特定类型。在您的情况下,重要的是与对应的存储变量的d类型std::decay_t<RequestData&>RequestData。在这种情况下,您可以executeRequest<int>使用lvalue 轻松调用RequestData

的要求要严格std::async得多:

要求:F并且每个TiArgs必须满足Cpp17MoveConstructible要求,并且

INVOKE(decay-copy(std::forward<F>(f)),
   decay-copy(std::forward<Args>(args))...)     // see [func.require], [thread.thread.constr]
Run Code Online (Sandbox Code Playgroud)

巨大的区别是衰减复制。对于d,您将获得以下信息:

decay-copy(std::forward<RequestData&>(d))
Run Code Online (Sandbox Code Playgroud)

这是对decay-copy函数的调用(仅博览会),其返回类型为std::decay_t<RequestData&>,所以RequestData,这就是编译失败的原因。


请注意,如果使用std::ref,则行为将是不确定的,因为的生存期d可能在调用之前结束executeRequest