tohava
tohava

Reputation: 5412

Implementing a function that perfect-forwards to std::thread

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

Answers (1)

Jonathan Wakely
Jonathan Wakely

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

Related Questions