I'm writing a async member function for my EventLoop class, which would posts the heavy computational task to a global thread pool, and then gets the result of that task and queues a finish callback function that takes the result of the previous work as a parameter back to the EventLoop to let the eventloop handle(call) the finish callback later.
So generally the async member function should takes two functions as the parameter, the first one is designed to be a task function of any return type, the second one is a finish callback that takes the return value of the first function as a parameter.
My first attempt code is as follows:
class EventLoop
{
public:
using DeferCallback=std::function<void()>;
//call by another thread to queue work into the eventloop
void queue_work(DeferCallback cb);
template<typename F,typename ...Args>
void async(F &&task_func,Args&& ...args,std::function<void(std::invoke_result_t<F,Args...>&)> &&finish_cb){
g_threadpool::get_instance()->post([this,task_func,args...,finish_cb](){
using task_ret_t=std::invoke_result_t<F,Args...>;
task_ret_t res=task_func(args...);
this->queue_work([finish_cb,&res](){
finish_cb(res);
});
});
}
//for task func of void return type
template<typename F,typename ...Args>
void async(F &&task_func,Args&& ...args,std::function<void(void)> &&finish_cb){
g_threadpool::get_instance()->post([this,task_func,args...,finish_cb](){
task_func(args...);
this->queue_work([finish_cb](){
finish_cb();
});
});
}
I tested and found that it only works when I don't pass anything to ...args, or the compiler couldn't work correctly. Then I do some search and find the following question: Parameter pack must be at the end of the parameter list... When and why? It basically tells me:
[...]If a template-parameter of a primary class template or alias >template is a template parameter pack, it shall be the last template->parameter.[...]
And if I have to explicitly instantiate the async function it will be hard to use.
Then I try this:
template<typename Ret,typename ...Args>
void async(std::function<Ret(Args...)> &&task_func,std::function<void(Ret&)> &&finish_cb){ ... }
and find out that I can't pass lambda function or member function except a std::function to the first parameter, which is not ideal. related question:Deduce template argument from std::function call signature
So is there any way to work around?
And if I have to explicitly instantiate the async function it will be hard to use.
In your case it's particularly hard because, given the signature
template <typename F, typename ... Args>
void async (F &&task_func,
Args && ...args,
std::function<void(std::invoke_result_t<F,Args...>&)> &&finish_cb)
(and ignoring the problem that Args... args
should be in last position) both F
and Args...
are present also in the type of the last argument (finish_cb
), so the deduction of both F
and Args...
depend also from the last parameter than must be coherent with preceding arguments.
So, by example, you can't pass a lambda to the last argument because a lambda can be converted to a std::function
but isn't a std::function
, so F
and Args...
can't be deduced from the lambda.
So is there any way to work around?
Suggestion: avoid at all std::function
and place the two callable argument before, the following (variadic) arguments after.
I mean... something as follows (but caution: code not tested and perfect forwarding ignored)
template <typename F1, typename F2, typename ...Args>
void async (F1 && task_func, F2 && finish_cb, Args && ... args) {
g_threadpool::get_instance()->post([this,task_func,args...,finish_cb](){
auto res=task_func(args...);
this->queue_work([finish_cb,&res](){
finish_cb(res);
});
});
}
If your problem is that you need two version of async()
, one where F1
return void
(doesn't return a value) and one where F1
return a value, you can use std::invoke_result_t
together with SFINAE
// void (no res) version
template <typename F1, typename F2, typename ...Args>
std::enable_if_t<true == std::is_same_v<void, std::invoke_result_t<F,Args...>>>
async (F1 && task_func, F2 && finish_cb, Args && ... args)
{ /* do something */ }
// non-void (with res) version
template <typename F1, typename F2, typename ...Args>
std::enable_if_t<false == std::is_same_v<void, std::invoke_result_t<F,Args...>>>
async (F1 && task_func, F2 && finish_cb, Args && ... args)
{ /* do something */ }