Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass variadic args to a std::thread?

I would like to use my own Thread implementation by wrapping the std::thread class from C++11 so I will be able handle exceptions like I want.

Here is my wrap class:

#include <Types.hpp>
#include <thread>
#include <exception>
#include <functional>

class Thread
{
    private:

        std::exception_ptr exceptionPtr;
        std::thread thread;

    public:

        using Id = std::thread::id;

        using NativeHandleType = std::thread::native_handle_type;

        Thread() noexcept = default;
        Thread(Thread &&t) noexcept :
            exceptionPtr(std::move(t.exceptionPtr)),
            thread(std::move(t.thread))
        {
        }

        Thread &operator =(Thread &&t) noexcept
        {
            exceptionPtr = std::move(t.exceptionPtr);
            thread = std::move(t.thread);
            return *this;
        }

        template<typename Callable, typename... Args>
        Thread(Callable &&f, Args &&... args) :
            exceptionPtr(nullptr),
            thread([&](Callable &&f, Args &&... args)
            {
                try
                {
                    std::once_flag flag;
                    std::call_once(flag, f, args...);
                }
                catch (...)
                {
                    exceptionPtr = std::current_exception();
                }

            }, f, args...)
        {
            if (exceptionPtr != nullptr)
            {
                 std::rethrow_exception(exceptionPtr);
            }
        }

        bool joinable() const noexcept
        {
            return thread.joinable();
        }

        void join()
        {
            thread.join();
        }

        void detach()
        {
            thread.detach();
        }

        Id getId() const noexcept
        {
            return thread.get_id();
        }

        NativeHandleType nativeHandle()
        {
            return thread.native_handle();
        }

        static uint32_t hardwareConcurrency() noexcept
        {
            return std::thread::hardware_concurrency();
        }

        static void wait(Time t)
        {
            std::this_thread::sleep_for(t);
        }
};

It works pretty well if there is no argument:

Thread([&]() {  /* do something */ }).detach();

... but if I try to pass variadic arguments:

Thread(&GUI::refreshTask, this, refreshDelay).detach();

... I get an error at compile time:

buildroot-2014.02/output/host/usr/i586-buildroot-linux-uclibc/include/c++/4.8.2/functional: In instantiation of 'struct std::_Bind_simple)(std::chrono::duration >); Args = {CRH::GUI const, std::chrono::duration >&}]::__lambda1(void (CRH::GUI::)(std::chrono::duration >), CRH::GUI, std::chrono::duration >)>': buildroot-2014.02/output/host/usr/i586-buildroot-linux-uclibc/include/c++/4.8.2/thread:137:47: required from 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = CRH::Thread::Thread(Callable&&, Args&& ...) [with Callable = void (CRH::GUI::)(std::chrono::duration >); Args = {CRH::GUI const, std::chrono::duration >&}]::__lambda1; _Args = {void (CRH::GUI::&)(std::chrono::duration >), CRH::GUI const&, std::chrono::duration >&}]' /home/cyril/Documents/crh-2016/src/robot2/../core/Thread.hpp:72:30: required from 'CRH::Thread::Thread(Callable&&, Args&& ...) [with Callable = void (CRH::GUI::)(std::chrono::duration >); Args = {CRH::GUI const, std::chrono::duration >&}]' src/core/GUI.cpp:90:57: required from here buildroot-2014.02/output/host/usr/i586-buildroot-linux-uclibc/include/c++/4.8.2/functional:1697:61: error: no type named 'type' in 'class std::result_of)(std::chrono::duration >); Args = {CRH::GUI const, std::chrono::duration >&}]::__lambda1(void (CRH::GUI::)(std::chrono::duration >), CRH::GUI, std::chrono::duration >)>' typedef typename result_of<_Callable(_Args...)>::type result_type; ^ buildroot-2014.02/output/host/usr/i586-buildroot-linux-uclibc/include/c++/4.8.2/functional:1727:9: error: no type named 'type' in 'class std::result_of)(std::chrono::duration >); Args = {CRH::GUI const, std::chrono::duration >&}]::__lambda1(void (CRH::GUI::)(std::chrono::duration >), CRH::GUI, std::chrono::duration >)>' _M_invoke(_Index_tuple<_Indices...>)

It could be a little clearer... but it will be too demanding for GCC.

Any idea how to fix this issue?

Solution

#include <Types.hpp>
#include <thread>
#include <exception>
#include <functional>

class Thread
{
    private:

        std::exception_ptr exceptionPtr;
        std::thread thread;

    public:

        using Id = std::thread::id;

        using NativeHandleType = std::thread::native_handle_type;

        Thread() noexcept = default;
        Thread(Thread &&t) noexcept :
            exceptionPtr(std::move(t.exceptionPtr)),
            thread(std::move(t.thread))
        {
        }

        Thread &operator =(Thread &&t) noexcept
        {
            exceptionPtr = std::move(t.exceptionPtr);
            thread = std::move(t.thread);
            return *this;
        }

        template<typename Callable, typename... Args>
        Thread(Callable &&f, Args &&... args) :
            exceptionPtr(nullptr),
            thread([&](typename std::decay<Callable>::type &&f, typename std::decay<Args>::type &&... args)
            {
                try
                {
                    std::bind(f, args...)();
                }
                catch (...)
                {
                    exceptionPtr = std::current_exception();
                }

            }, std::forward<Callable>(f), std::forward<Args>(args)...)
        {
        }

        bool joinable() const noexcept
        {
            return thread.joinable();
        }

        void join()
        {
            thread.join();

            if (exceptionPtr != nullptr)
            {
                std::rethrow_exception(exceptionPtr);
            }
        }

        void detach()
        {
            thread.detach();
        }

        Id getId() const noexcept
        {
            return thread.get_id();
        }

        NativeHandleType nativeHandle()
        {
            return thread.native_handle();
        }

        static uint32_t hardwareConcurrency() noexcept
        {
            return std::thread::hardware_concurrency();
        }

        static void wait(Time t)
        {
            std::this_thread::sleep_for(t);
        }
};
like image 207
didil Avatar asked Sep 26 '22 17:09

didil


1 Answers

Callable and Args are forwarding references, thus template argument deduction can make them either lvalue references or plain types, depending on the value category of argument expressions.

This means that when you reuse the deduced types in the declaration of a lambda:

thread([&](Callable&& f, Args&&... args)

reference collapsing comes into play and, for an lvalue argument refreshDelay, Args becomes an lvalue reference.

However, std::thread stores decayed-copies of the arguments it receives, and then it moves from its internal storage to an actual handler, turning the stored objects into xvalues. This is what the error tells you: the handler is not callable with the arguments the thread tries to pass in.

Instead, you can implement it as follows:

template <typename Callable, typename... Args>
Thread(Callable&& f, Args&&... args)
    : exceptionPtr(nullptr)
    , thread([] (typename std::decay<Callable>::type&& f
               , typename std::decay<Args>::type&&... args)
            {
                // (...)
            }
            , std::forward<Callable>(f), std::forward<Args>(args)...)
{
    // (...)
}
like image 190
Piotr Skotnicki Avatar answered Oct 11 '22 12:10

Piotr Skotnicki