Search code examples
c++c++11stdthread

How to properly extend std::thread to wrap spawned thread


Im trying to create a simple wrapper to be used by my application when creating threads in lew of std::thread. The only purpose of using this app thread wrapper is to ensure some code gets invoked on every spawned thread consistently. I thought this would be trivial, but the std::thread constructor and argument passing is rather complex and Im getting very cryptic build errors here.

This is a simple example of what I'm trying to do:

#include <thread>

class AppThread : public std::thread
{
    template< class Function, class... Args > 
    static void wrap( Function&& f, Args&&... args )
    {
        //Some code
        f( std::forward<Args>( args )... );
    }

public:
    template< class Function, class... Args > 
    explicit AppThread( Function&& f, Args&&... args ) : std::thread( AppThread::wrap<Function,Args...>,
                                                                      std::forward<Function>( f ), std::forward<Args>( args )... )
    {}
};

void runA() {}
void runB( int x ) {}

main()
{
    AppThread thread1 = AppThread( runA );
    //AppThread thread2 = AppThread( runB, 5 );
}

I'd like to be able to drop in AppThread wherever std:thread is being used, so extending and overriding the constructor seems like the best approach. But passing those arguments through to my wrapped method causes this cascade of errors ( gcc 7.2 )

   In file included from thread_wrap.cpp:1:0:
gcc-7.2.0/include/c++/7.2.0/thread: In instantiation of ‘struct std::thread::_Invoker<std::tuple<void (*)(void (&)()), void (*)()> >’:
gcc-7.2.0/include/c++/7.2.0/thread:127:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(void (&)()); _Args = {void (&)()}]’
thread_wrap.cpp:15:130:   required from ‘AppThread::AppThread(Function&&, Args&& ...) [with Function = void (&)(); Args = {}]’
thread_wrap.cpp:24:41:   required from here
gcc-7.2.0/include/c++/7.2.0/thread:240:2: error: no matching function for call to ‘std::thread::_Invoker<std::tuple<void (*)(void (&)()), void (*)()> >::_M_invoke(std::thread::_Invoker<std::tuple<void (*)(void (&)()), void (*)()> >::_Indices)’
  operator()()
  ^~~~~~~~
gcc-7.2.0/include/c++/7.2.0/thread:231:4: note: candidate: template<long unsigned int ..._Ind> decltype (std::__invoke((_S_declval<_Ind>)()...)) std::thread::_Invoker<_Tuple>::_M_invoke(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {_Ind ...}; _Tuple = std::tuple<void (*)(void (&)()), void (*)()>]
    _M_invoke(_Index_tuple<_Ind...>)
    ^~~~~~~~~
gcc-7.2.0/include/c++/7.2.0/thread:231:4: note:   template argument deduction/substitution failed:
gcc-7.2.0/include/c++/7.2.0/thread: In substitution of ‘template<long unsigned int ..._Ind> decltype (std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void (*)(void (&)()), void (*)()> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind1 ...>) [with long unsigned int ..._Ind = {0, 1}]’:
gcc-7.2.0/include/c++/7.2.0/thread:240:2:   required from ‘struct std::thread::_Invoker<std::tuple<void (*)(void (&)()), void (*)()> >’
gcc-7.2.0/include/c++/7.2.0/thread:127:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(void (&)()); _Args = {void (&)()}]’
thread_wrap.cpp:15:130:   required from ‘AppThread::AppThread(Function&&, Args&& ...) [with Function = void (&)(); Args = {}]’
thread_wrap.cpp:24:41:   required from here
gcc-7.2.0/include/c++/7.2.0/thread:233:29: error: no matching function for call to ‘__invoke(std::__tuple_element_t<0, std::tuple<void (*)(void (&)()), void (*)()> >, std::__tuple_element_t<1, std::tuple<void (*)(void (&)()), void (*)()> >)’
    -> decltype(std::__invoke(_S_declval<_Ind>()...))
                ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
In file included from gcc-7.2.0/include/c++/7.2.0/tuple:41:0,
                 from gcc-7.2.0/include/c++/7.2.0/bits/unique_ptr.h:37,
                 from gcc-7.2.0/include/c++/7.2.0/memory:80,
                 from gcc-7.2.0/include/c++/7.2.0/thread:39,
                 from thread_wrap.cpp:1:
gcc-7.2.0/include/c++/7.2.0/bits/invoke.h:89:5: note: candidate: template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...)
     __invoke(_Callable&& __fn, _Args&&... __args)
     ^~~~~~~~
gcc-7.2.0/include/c++/7.2.0/bits/invoke.h:89:5: note:   template argument deduction/substitution failed:
gcc-7.2.0/include/c++/7.2.0/bits/invoke.h: In substitution of ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...) [with _Callable = void (*)(void (&)()); _Args = {void (*)()}]’:
gcc-7.2.0/include/c++/7.2.0/thread:233:29:   required by substitution of ‘template<long unsigned int ..._Ind> decltype (std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void (*)(void (&)()), void (*)()> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind1 ...>) [with long unsigned int ..._Ind = {0, 1}]’
gcc-7.2.0/include/c++/7.2.0/thread:240:2:   required from ‘struct std::thread::_Invoker<std::tuple<void (*)(void (&)()), void (*)()> >’
gcc-7.2.0/include/c++/7.2.0/thread:127:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(void (&)()); _Args = {void (&)()}]’
thread_wrap.cpp:15:130:   required from ‘AppThread::AppThread(Function&&, Args&& ...) [with Function = void (&)(); Args = {}]’
thread_wrap.cpp:24:41:   required from here
gcc-7.2.0/include/c++/7.2.0/bits/invoke.h:89:5: error: no type named ‘type’ in ‘struct std::__invoke_result<void (*)(void (&)()), void (*)()>’

Could definitely use some insight here! Thanks


Solution

  • I figured this out when I woke up this morning :) Since std::thread decays all arguments it passes to the function, by the time it calls my wrap method the arguments are the decayed types. However, when I'm passing the template args to wrap<> it gets the undecayed types:

    std::thread( AppThread::wrap<Function,Args...>,  
    

    Solution is simple, I need decay the types before instantiation the template wrap method:

    std::thread( AppThread::wrap<std::decay_t<Function>,std::decay_t<Args>...>,