【问题标题】:Adding another function as a parameter after F&&,Args &&... parameters in a template function C++在模板函数 C++ 中的 F&&,Args &&... 参数之后添加另一个函数作为参数
【发布时间】:2020-01-04 20:34:39
【问题描述】:

我正在为我的 EventLoop 类编写一个异步成员函数,它将繁重的计算任务发布到全局线程池,然后获取该任务的结果并将一个完成回调函数排队,该函数将获取前一个结果作为返回 EventLoop 的参数,让 eventloop 稍后处理(调用)完成回调。

所以一般来说async成员函数应该带两个函数作为参数,第一个被设计成任意返回类型的任务函数,第二个是一个finish回调,它把第一个函数的返回值作为一个范围。

我的第一次尝试代码如下:

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();
        });
    });
}

我测试并发现它只有在我不向...args传递任何东西时才有效,或者编译器无法正常工作。然后我做了一些搜索并找到以下问题:Parameter pack must be at the end of the parameter list... When and why? 它基本上告诉我:

[...]如果主类模板或别名>template的模板参数是模板参数包,它应该是最后一个模板->参数。[...]

如果我必须显式实例化异步函数,它将很难使用。

然后我试试这个:

template<typename Ret,typename ...Args>
void async(std::function<Ret(Args...)> &&task_func,std::function<void(Ret&)> &&finish_cb){ ... }

并发现我无法将 lambda 函数或成员函数(除了 std::function 之外)传递给第一个参数,这并不理想。相关问题:Deduce template argument from std::function call signature

那么有什么办法可以解决吗?

【问题讨论】:

    标签: c++ c++11 templates variadic-templates


    【解决方案1】:

    如果我必须显式实例化异步函数,它将很难使用。

    在您的情况下,这特别困难,因为鉴于签名

    template <typename F, typename ... Args>
    void async (F &&task_func,
                Args && ...args,
                std::function<void(std::invoke_result_t<F,Args...>&)> &&finish_cb)
    

    (忽略Args... args应该在最后一个位置的问题)FArgs...也存在于最后一个参数(finish_cb)的类型中,所以F的推导和Args... 也依赖于最后一个参数,而不是必须与前面的参数保持一致。

    因此,例如,您不能将 lambda 传递给最后一个参数,因为 lambda 可以转换为 std::function 但不是 std::function,所以 FArgs... 不能从 lambda 推导出来。

    那么有什么办法可以解决吗?

    建议:完全避免 std::function 并将两个可调用参数放在前面,后面的(可变参数)参数放在后面。

    我的意思是...如下所示(但请注意:代码未经测试,完美转发被忽略)

    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);
            });
        });
    }
    

    如果您的问题是您需要两个版本的async(),一个是F1 返回void(不返回值),另一个是F1 返回一个值,您可以使用std::invoke_result_t与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 */ }
    

    【讨论】:

    • 感谢您的建议,这是一个可以接受的选项,并且将 invoke_result_t 与 SFINAE 一起使用更优雅
    猜你喜欢
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    相关资源
    最近更新 更多