Reputation: 5412
I am trying to write a wrapper around std::thread
:
#include <thread>
#include <iostream>
struct A {};
template <typename F, typename... Args>
void lifted_lambda_1(void *m, F &&entrypoint, Args&&... args) {
std::cout << "I will do something with the void * " << m << std::endl;
entrypoint(std::forward<Args>(args)...);
}
template <typename F, typename... Args>
void make_thread(void *p, F &&f, Args && ... args) {
std::thread(lifted_lambda_1<typename std::decay<F>::type, Args...>, p, std::forward<F>(f), std::forward<Args>(args)...).detach();
}
int main() {
A a;
make_thread(nullptr, [](A x){}, a);
}
But when I compile it I get an error:
In file included from /usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/thread:39:0,
from bubu.cpp:1:
/usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/functional: In instantiation of ‘struct std::_Bind_simple<void (*(void*, main()::__lambda0, A))(void*, main()::__lambda0&&, A&)>’:
/usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/thread:137:47: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(void*, main()::__lambda0&&, A&); _Args = {void*&, main()::__lambda0, A&}]’
bubu.cpp:15:132: required from ‘void make_thread(void*, F&&, Args&& ...) [with F = main()::__lambda0; Args = {A&}]’
bubu.cpp:20:38: required from here
/usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of<void (*(void*, main()::__lambda0, A))(void*, main()::__lambda0&&, A&)>’
typedef typename result_of<_Callable(_Args...)>::type result_type;
^
/usr/local/sqream-prerequisites/package-install/gcc-4.8.2/include/c++/4.8.2/functional:1727:9: error: no type named ‘type’ in ‘class std::result_of<void (*(void*, main()::__lambda0, A))(void*, main()::__lambda0&&, A&)>’
_M_invoke(_Index_tuple<_Indices...>)
What is the reason for this error? How do I fix it?
Upvotes: 3
Views: 919
Reputation: 171333
std::thread
will always decay its arguments (for the reasons given at the link in one of the comments above). You can use reference_wrapper
to protect references, so that arguments can be passed by lvalue reference.
To make that work with both lvalue and rvalue arguments you need a wrapper function which will wrap lvalues in reference_wrapper
but allow rvalues to be copied (or moved) and forwarded as rvalues. This will not be "perfect" forwarding, because rvalues will be copied, not forwarded as rvalue references, so the target function gets called with new objects.
So you can use something like this to conditionally wrap lvalues but just forward rvalues:
template<typename T>
std::reference_wrapper<std::remove_reference_t<T>>
wrap(T& t) { return std::ref(t); }
template<typename T>
T&&
wrap(typename std::remove_reference<T>::type&& t)
{ return std::move(t); }
(remove_reference
is used on the second overload so that T
is in a non-deduced context, and so that the argument is not a forwarding reference).
Then use that for the arguments to the thread constructor:
std::thread(lifted_lambda_1<typename std::decay<F>::type, Args...>, p,
std::forward<F>(f), wrap<Args>(args)...).detach();
/*^^^^^^^^^^^^*/
However, doing this brings back all the problems that std::thread
tries to avoid by copying its arguments! You must ensure that any lvalues passed to make_thread
will not go out of scope before the thread finishes running. Since you are detaching the thread, that is very difficult to do in general. You must be very careful when using this function.
Potentially you could write your own class template that behaves like reference_wrapper
that protects rvalue references, to avoid the new objects being created, but then you must also be careful that the rvalue arguments to the thread function do not go out of scope before the thread finishes running (and if they are rvalues there is a high probability that they are temporaries which will not outlive the call that creates the new thread!)
Here be dragons.
Upvotes: 4