I have a simple thread pool. It takes tasks and distributes them among threads using round-robin. The task looks like this
using TaskFn = void (*)(void*);
struct Task {
TaskFn fn;
void* args;
};
Just two pointers: to a function that takes void*
and to the argument itself. The thread pool calls Task::fn
and passes Task::args
. Everything works well.
But I wanted to write a typed wrapper for this whole thing. So I could write like this:
Task some_task = MakeTask([](int a, int b){
// Task body
}, 123, 456);
I don’t need the closures to work. I wrote code that does not compile:
template <typename Function, typename ... Args>
void DoCall(Function f, std::tuple<Args...>* args) {
auto callable = [args, f](){
std::apply(f, *args);
};
callable();
}
template <typename Function, typename ... Args>
Task MakeTask(Function f, Args ... args) {
Task task;
std::tuple<Args...>* args_on_heap = new std::tuple<Args...>(args...);
task.args = (void*) args_on_heap;
TaskFn fn = [](void* ptr){
// The problem here is that I can’t pass `f` here without creating a closure.
// But if I create a closure, the signature will be different.
// In theory, everything that is needed is known at the compilation stage.
// But how to curb the compiler?
DoCall<Function, Args...>(f, (std::tuple<Args...>*) ptr);
};
task.fn = fn;
return task;
// P.S I know that args_on_heap leaks.
}
So, questions:
Thank you in advance :)
As written, you’re accepting even callable objects with state, so the language will force you to account for that state. You could copy it into a control block along with your arguments, but if support for state isn’t the point you can require the function to be a template argument:
template <auto &F, typename ... Args>
Task MakeTask(Args ... args) {
using T = std::tuple<Args...>;
return {[](void* ptr){
std::unique_ptr<T> p (static_cast<T*>(ptr));
DoCall<F, Args...>(f, p.get());
}, new T(args...)};
}
Note that passing a lambda directly as a template argument requires C++20. You can work around that with a constexpr variable:
constexpr auto *f = [](…) {…};
MakeTask<*f>(…);