Reputation: 7832
I'm trying to subclass std::thread
such that a member function of the subclass is executed on the new thread before the caller's passed-in function. Something like the following invalid code:
#include <thread>
#include <utility>
class MyThread : public std::thread {
template<class Func, class... Args>
void start(Func&& func, Args&&... args) {
... // Useful, thread-specific action
func(args...);
}
public:
template<class Func, class... Args>
MyThread(Func&& func, Args&&... args)
: std::thread{[=]{start(std::forward<Func>(func),
std::forward<Args>(args)...);}} {
}
};
g++ -std=c++11
has the following issue with the above code:
MyThread.h: In lambda function:
MyThread.h:ii:jj: error: parameter packs not expanded with '...':
std::forward<Args>(args)...);}}
^
I've tried a dozen different variations in the initializer-list to no avail.
How can I do what I want?
Upvotes: 0
Views: 158
Reputation: 4692
The biggest difficulty I had when I did this before was getting all the behavior of std::thread
. It's constructor can take not only a pointer to a free function, but also a class method pointer and then a object of the class as the first argument. There are a number of variations on that: class methods, class function object data members, an object of the class type vs a pointer to an object, etc.
This is for C++14:
class MyThread : public std::thread {
void prolog() const { std::cout << "prolog\n"; }
public:
template <typename... ArgTypes>
MyThread(ArgTypes&&... args) :
std::thread(
[this, bfunc = std::bind(std::forward<ArgTypes>(args)...)]
() mutable {
prolog();
bfunc();
})
{ }
};
If you put the prolog code inside the lambda and it doesn't call class methods, then the capture of this
is not needed.
For C++11, a small change is needed because of the lack of capture initializers, so the bind must be passed as an argument to std::thread
:
std::thread(
[this]
(decltype(std::bind(std::forward<ArgTypes>(args)...))&& bfunc) mutable {
prolog();
bfunc();
}, std::bind(std::forward<ArgTypes>(args)...))
Here's a test program that also exercises the class member form of std::thread
:
int main()
{
auto x = MyThread([](){ std::cout << "lambda\n"; });
x.join();
struct mystruct {
void func() { std::cout << "mystruct::func\n"; }
} obj;
auto y = MyThread(&mystruct::func, &obj);
y.join();
return 0;
}
I haven't checked, but I'm a bit worried that the capture of this
, also seen in other solutions, is not safe in some cases. Consider when the object is an rvalue that is moved, as in std::thread t = MyThread(args)
. I think the MyThread object will go away before the thread it has created is necessarily finished using it. The "thread" will be moved into a new object and still be running, but the captured this pointer will point to a now stale object.
I think you need to insure your constructor does not return until your new thread is finished using all references or pointers to the class or class members. Capture by value, when possible, would help. Or perhaps prolog()
could be a static class method.
Upvotes: 1
Reputation: 69892
This should do it (c++11 and c++14 solutions provided):
C++14
#include <thread>
#include <utility>
#include <tuple>
class MyThread : public std::thread {
template<class Func, class ArgTuple, std::size_t...Is>
void start(Func&& func, ArgTuple&& args, std::index_sequence<Is...>) {
// Useful, thread-specific action
func(std::get<Is>(std::forward<ArgTuple>(args))...);
}
public:
template<class Func, class... Args>
MyThread(Func&& func, Args&&... args)
: std::thread
{
[this,
func = std::forward<Func>(func),
args = std::make_tuple(std::forward<Args>(args)...)] () mutable
{
using tuple_type = std::decay_t<decltype(args)>;
constexpr auto size = std::tuple_size<tuple_type>::value;
this->start(func, std::move(args), std::make_index_sequence<size>());
}
}
{
}
};
int main()
{
auto x = MyThread([]{});
}
In C++17 it's trivial:
#include <thread>
#include <utility>
#include <tuple>
#include <iostream>
class MyThread : public std::thread {
public:
template<class Func, class... Args>
MyThread(Func&& func, Args&&... args)
: std::thread
{
[this,
func = std::forward<Func>(func),
args = std::make_tuple(std::forward<Args>(args)...)] () mutable
{
std::cout << "execute prolog here" << std::endl;
std::apply(func, std::move(args));
std::cout << "execute epilogue here" << std::endl;
}
}
{
}
};
int main()
{
auto x = MyThread([](int i){
std::cout << i << std::endl;
}, 6);
x.join();
}
C++11 (we have to facilitate moving objects into the mutable lambda, and provide the missing std::index_sequence):
#include <thread>
#include <utility>
#include <tuple>
namespace notstd
{
using namespace std;
template<class T, T... Ints> struct integer_sequence
{};
template<class S> struct next_integer_sequence;
template<class T, T... Ints> struct next_integer_sequence<integer_sequence<T, Ints...>>
{
using type = integer_sequence<T, Ints..., sizeof...(Ints)>;
};
template<class T, T I, T N> struct make_int_seq_impl;
template<class T, T N>
using make_integer_sequence = typename make_int_seq_impl<T, 0, N>::type;
template<class T, T I, T N> struct make_int_seq_impl
{
using type = typename next_integer_sequence<
typename make_int_seq_impl<T, I+1, N>::type>::type;
};
template<class T, T N> struct make_int_seq_impl<T, N, N>
{
using type = integer_sequence<T>;
};
template<std::size_t... Ints>
using index_sequence = integer_sequence<std::size_t, Ints...>;
template<std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;
}
template<class T>
struct mover
{
mover(T const& value) : value_(value) {}
mover(T&& value) : value_(std::move(value)) {}
mover(const mover& other) : value_(std::move(other.value_)) {}
T& get () & { return value_; }
T&& get () && { return std::move(value_); }
mutable T value_;
};
class MyThread : public std::thread {
template<class Func, class ArgTuple, std::size_t...Is>
void start(Func&& func, ArgTuple&& args, notstd::index_sequence<Is...>) {
// Useful, thread-specific action
func(std::get<Is>(std::forward<ArgTuple>(args))...);
}
public:
template<class Func, class... Args>
MyThread(Func&& func, Args&&... args)
: std::thread()
{
using func_type = typename std::decay<decltype(func)>::type;
auto mfunc = mover<func_type>(std::forward<Func>(func));
using arg_type = decltype(std::make_tuple(std::forward<Args>(args)...));
auto margs = mover<arg_type>(std::make_tuple(std::forward<Args>(args)...));
static_cast<std::thread&>(*this) = std::thread([this, mfunc, margs]() mutable
{
using tuple_type = typename std::remove_reference<decltype(margs.get())>::type;
constexpr auto size = std::tuple_size<tuple_type>::value;
this->start(mfunc.get(), std::move(margs).get(), notstd::make_index_sequence<size>());
});
}
};
int main()
{
auto x = MyThread([](int i){}, 6);
x.join();
}
Upvotes: 1