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:
#include <thread>
#include <future>
#include <utility>
#include <iostream>
#include <vector>
/**
* Function template that does perfect forwarding to a lambda inside an
* async call (or at least tries to). I want both instantiations of the
* function to work (one for lvalue references T&, and rvalue reference T&&).
* However, I cannot get the code to compile when calling it with an lvalue.
* See main() below.
*/
template <typename T>
std::string accessValueAsync(T&& obj)
{
std::future<std::string> fut =
std::async(std::launch::async,
[](T&& vec) mutable
{
return vec[0];
},
std::forward<T>(obj));
return fut.get();
}
int main(int argc, char const *argv[])
{
std::vector<std::string> lvalue{"Testing"};
// calling with what I assume is an lvalue reference does NOT compile
std::cout << accessValueAsync(lvalue) << std::endl;
// calling with rvalue reference compiles
std::cout << accessValueAsync(std::move(lvalue)) << std::endl;
// I want both to compile.
return 0;
}
For the non-compiling case, here is the last line of the error message which is intelligible:
main.cpp|13 col 29| note: no known conversion for argument 1 from ‘std::vector<std::basic_string<char> >’ to ‘std::vector<std::basic_string<char> >&’
I have a feeling it may have something to do with how T&&
is deduced, but I can't pinpoint the exact point of failure and fix it. Any suggestions?
Thank you!
EDIT: I am using gcc 4.7.0 just in case this could be a compiler issue (probably not)
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<F>(f)), DECAY_COPY (std::forward<Args>(args))...)
(20.8.2, 30.3.1.2) shall be a valid expression.(3) Effects: [...] if policy & launch::async is non-zero — calls
INVOKE(DECAY_COPY (std::forward<F>(f)),DECAY_COPY (std::forward<Args>(args))...)
(20.8.2, 30.3.1.2) as if in a new thread of execution represented by a thread object with the calls toDECAY_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 <thread>
#include <future>
#include <utility>
#include <iostream>
#include <vector>
#include <type_traits>
/* First the two definitions of wrap_lval (one for rvalue references,
the other for lvalue references). */
template <typename T>
constexpr T&&
wrap_lval(typename std::remove_reference<T>::type &&obj) noexcept
{ return static_cast<T&&>(obj); }
template <typename T>
constexpr std::reference_wrapper<typename std::remove_reference<T>::type>
wrap_lval(typename std::remove_reference<T>::type &obj) noexcept
{ return std::ref(obj); }
/* The following is your code, except for one change. */
template <typename T>
std::string accessValueAsync(T&& obj)
{
std::future<std::string> fut =
std::async(std::launch::async,
[](T&& vec) mutable
{
return vec[0];
},
wrap_lval<T>(std::forward<T>(obj))); // <== Passing obj through wrap_lval
return fut.get();
}
int main(int argc, char const *argv[])
{
std::vector<std::string> 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With