I have a function template, where I want to do perfect forwarding into a lambda that I run on another thread. Here is a minimal test case which you can directly compile:
The way I understand it you cannot use a function through async
that expects non-const lvalue references as arguments, because async
will always make a copy of them internally (or move them inside) to ensure they exist and are valid throughout the running time of the thread created.
Specifically, the Standard says about async(launch policy, F&& f, Args&&... args)
:
(§30.6.8)
(2) Requires:
F
and eachTi
inArgs
shall satisfy the MoveConstructible requirements.INVOKE(DECAY_COPY (std::forward
(20.8.2, 30.3.1.2) shall be a valid expression.(f)), DECAY_COPY (std::forward (args))...) (3) Effects: [...] if policy & launch::async is non-zero — calls
INVOKE(DECAY_COPY (std::forward
(20.8.2, 30.3.1.2) as if in a new thread of execution represented by a thread object with the calls to(f)),DECAY_COPY (std::forward (args))...) DECAY_COPY()
being evaluated in the thread that called async. Any return value is stored as the result in the shared state. Any exception propagated from the execution of INVOKE (DECAY_COPY (std::forward(f)), DECAY_COPY (std::forward(args))...) is stored as the exceptional result in the shared state.
The thread object is stored in the shared state and affects the behavior of any asynchronous return objects that reference that state.
Unfortunately, this means you cannot even replace the reference with a std::reference_wrapper
, because the latter isn't move-constructible. I suppose using a std::unique_ptr
instead of the reference would work (implying, however, that your function arguments will always live on the heap).
(EDIT/CORRECTION)
I was working on a related problem when I realized that std::reference_wrapper
actually enables a workaround, although I claimed the opposite above.
If you define a function that wraps lvalue references in a std::reference_wrapper
, but leaves rvalue references unchanged, you can pass the T&&
argument through this function before handing it over to std::async
. I have called this special wrapper function wrap_lval
below:
#include
#include
#include
#include
#include
#include
/* First the two definitions of wrap_lval (one for rvalue references,
the other for lvalue references). */
template
constexpr T&&
wrap_lval(typename std::remove_reference::type &&obj) noexcept
{ return static_cast(obj); }
template
constexpr std::reference_wrapper::type>
wrap_lval(typename std::remove_reference::type &obj) noexcept
{ return std::ref(obj); }
/* The following is your code, except for one change. */
template
std::string accessValueAsync(T&& obj)
{
std::future fut =
std::async(std::launch::async,
[](T&& vec) mutable
{
return vec[0];
},
wrap_lval(std::forward(obj))); // <== Passing obj through wrap_lval
return fut.get();
}
int main(int argc, char const *argv[])
{
std::vector lvalue{"Testing"};
std::cout << accessValueAsync(lvalue) << std::endl;
std::cout << accessValueAsync(std::move(lvalue)) << std::endl;
return 0;
}
With this change, both calls to accessValueAsync
compile and work. The first one, which uses an lvalue reference, automatically wraps it in a std::reference_wrapper
. The latter is automatically converted back to an lvalue reference when std::async
calls the lambda function.