Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing forwarding references to thread wrapper class

Let's take a look at the concrete functions first:

void boo1 () { std::cout << "boo1\n"; }
void boo2 (std::string) { std::cout << "boo2\n"; }

struct X {
    void boo3 () { std::cout << "boo3\n"; }
    void boo4 (std::string) { std::cout << "boo4\n"; }
};

I want those functions to be executed with some "guard" function, which does exception protection (explanation below). So, I have written two functions one for free functions and the other for member functions:

template <typename C, typename... Args>
typename std::enable_if<!std::is_member_function_pointer<C>::value, void>::type
foo (C&& c, Args&&... args) {
    std::cout << "not memfun\n";
    c (std::forward<Args> (args)...);
}

template <typename C, typename T, typename... Args>
typename std::enable_if<std::is_member_function_pointer<C>::value, void>::type
foo (C&& c, T* o, Args&&... args) {
    std::cout << "memfun\n";
    (o->*c) (std::forward<Args> (args)...);
}

And now I want to execute boo1(), boo2(), X::boo3() and X::boo4() in new threads but with "protection" used by foo() functions. So, without any problem I can do this with:

X x;
std::thread th1 {&foo<void (*) ()>, &boo1};
std::thread th2 {&foo<void (*) (std::string), std::string>, &boo2, "Hello"};
std::thread th3 {&foo<void (X::*) (), X>, &X::boo3, &x};
std::thread th4 {&foo<void (X::*) (std::string), X, std::string>, &X::boo4, &x, "Hello"};

So, going one step further I have written thread wrapper class to stop using explicitly std::join() or std::detach(). Insted one of them is invoked when destructor is in action. For the simplicity of this example I have only used one of them. So, the code looks like this:

class Th {
public:
    template <typename C, 
              typename = typename std::enable_if<!std::is_member_function_pointer<C>::value, void>::type,
              typename... Args>
    Th (C&& c, Args... args) // (X)
    : t {foo<C, Args...>, std::forward<C> (c), std::forward<Args> (args)...} {}

    template <typename C,
              typename = typename std::enable_if<std::is_member_function_pointer<C>::value, void>::type,
              typename T,
              typename... Args>
    Th (C&& c, T* o, Args... args) // (Y)
    : t { foo<C, T, Args...>, std::forward<C> (c), o, std::forward<Args> (args)...} {}


    ~Th () { t.join (); }

private:
    std::thread t;
};

And with this implementation there is no problem, the following usage cases:

X x;
Th h1 {&boo1};
Th h2 {&boo2, "Hello"};
Th h3 {&X::boo3, &x};
Th h4 {&X::boo4, &x, "Hello"};

work as excepcted. However there is one flaw here - no perfect forwarding. If I change in (X) and (Y) Args... to Args&&... which is desired, the code does not compile with some errors, one of them is:

error: no type named ‘type’ in ‘class std::result_of<void (*(void (*)(std::basic_string<char>), const char*))(void (*&&)(std::basic_string<char>), const char (&)[6])>’

which if I understand it correctly, std::bind() in std::thread constructor can not do it's job, or correct me if I am wrong.

My question is: what can I do to take advantage of perfect forwarding in this case?

Compiler used to compile this was GCC 4.8.1.

Promised explanation: I want foo() functions to surround call to c() with try and catch to make sure that the exception is catched, process this information in some kind of logging mechanism, and rethrow the exception if needed with the transparency to the user of Th class.

like image 640
Artur Pyszczuk Avatar asked Nov 17 '17 11:11

Artur Pyszczuk


1 Answers

When passing lvalue references to std::thread, you need to wrap them in std::reference_wrapper. For example, you can do it in the following way:

namespace detail
{

template<typename T>
std::reference_wrapper<T> forward_to_thread(T& t, std::true_type) {
    return std::ref(t);
}

template<typename T>
T&& forward_to_thread(T&& t, std::false_type) {
    return std::move(t);
}

}

template<typename T>
auto forward_to_thread(T&& t)
    -> decltype(detail::forward_to_thread(std::forward<T>(t), std::is_lvalue_reference<T>{}))
{
    return detail::forward_to_thread(std::forward<T>(t), std::is_lvalue_reference<T>{});
}

Then you just replace std::forward calls to forward_to_thread in thread initialization:

class Th {
public:
    template <typename C,
              typename = typename std::enable_if<!std::is_member_function_pointer<C>::value, void>::type,
              typename... Args>
    Th (C&& c, Args&&... args) // (X)
    : t {foo<C, Args...>, std::forward<C> (c), forward_to_thread<Args> (std::forward<Args>(args))...} {}

    template <typename C,
              typename = typename std::enable_if<std::is_member_function_pointer<C>::value, void>::type,
              typename T,
              typename... Args>
    Th (C&& c, T* o, Args&&... args) // (Y)
    : t { foo<C, T, Args...>, std::forward<C> (c), o, forward_to_thread<Args> (std::forward<Args>(args))...} {}


    ~Th () { t.join (); }

private:
    std::thread t;
};

This does work with gcc 4.8.2 [link]

If you can use a newer compiler, consider using std::invoke (C++17) for more succint and readable code (i.e. this).

like image 182
krzaq Avatar answered Oct 06 '22 23:10

krzaq