I'm attempting to implement std::async
from scratch, and have run into a hiccup with arguments of move-only type. The gist of it is, C++14 init-captures allow us to capture single variables "by move" or "by perfect forwarding", but they do not appear to let us capture parameter packs "by move" nor "by perfect forwarding", because you can't capture a parameter pack by init-capture — only by named capture.
I've found what appears to be a workaround, by using std::bind
to capture the parameter pack "by move", and then using a wrapper to move the parameters out of the bind object's storage into the parameter slots of the function I really want to call. It even looks elegant, if you don't think too much about it. But I can't help thinking that there must be a better way — ideally one that doesn't rely on std::bind
at all.
(Worst case, I'd like to know how much of std::bind
I'd have to reimplement on my own in order to get away from it. Part of the point of this exercise is to show how things are implemented all the way down to the bottom, so having a dependency as complicated as std::bind
really sucks.)
My questions are:
How do I make my code work, without using
std::bind
? (I.e., using only core language features. Generic lambdas are fair game.)Is my
std::bind
workaround bulletproof? That is, can anybody show an example where the STL'sstd::async
works and myAsync
fails?Pointers to discussion and/or proposals to support parameter-pack capture in C++1z will be gratefully accepted.
template<typename UniqueFunctionVoidVoid>
auto FireAndForget(UniqueFunctionVoidVoid&& uf)
{
std::thread(std::forward<UniqueFunctionVoidVoid>(uf)).detach();
}
template<typename Func, typename... Args>
auto Async(Func func, Args... args)
-> std::future<decltype(func(std::move(args)...))>
{
using R = decltype(func(std::move(args)...));
std::packaged_task<R(Args...)> task(std::move(func));
std::future<R> result = task.get_future();
#ifdef FAIL
// sadly this syntax is not supported
auto bound = [task = std::move(task), args = std::move(args)...]() { task(std::move(args)...) };
#else
// this appears to work
auto wrapper = [](std::packaged_task<R(Args...)>& task, Args&... args) { task(std::move(args)...); };
auto bound = std::bind(wrapper, std::move(task), std::move(args)...);
#endif
FireAndForget(std::move(bound));
return result;
}
int main()
{
auto f3 = [x = std::unique_ptr<int>{}](std::unique_ptr<int> y) -> bool { sleep(2); return x == y; };
std::future<bool> r3 = Async(std::move(f3), std::unique_ptr<int>{});
std::future<bool> r4 = Async(std::move(f3), std::unique_ptr<int>(new int));
assert(r3.get() == true);
assert(r4.get() == false);
}