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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With