【问题标题】:Deduct Parameter Pack Arguments for Lambda Functor减去 Lambda 函子的参数包参数
【发布时间】:2020-11-18 21:31:19
【问题描述】:

我有一个类,我想用它在 EventQueue 上执行回调:

template <class, class>
class Handler;

template <class Fun, class... Args>
class Handler<void(Args...)>
{
public:
    Handler(events::EventQueue &eq, Fun fun) : _eq(eq), _fun(fun)
    {}

    void operator()(Args... args)
    {
        _eq.call(this, &Handler::callInternal, args...);
    }

private:
    events::EventQueue &_eq;
    Fun _fun;

    void callInternal(Args... args)
    {
        _fun(args...);
    }
};

其中 EventQueue::call(...) 接受一个对象指针、一个方法指针和用于调用该方法的参数。

我真的很想用右值兰巴表达式实例化一个 Handler 类型的对象,例如

makeHandler([](int result) { ... });

我不在乎是否必须使用辅助方法,例如makeHandler 来构造Handler。

但是这些方面的东西对我不起作用:

template <class Fun, class... Args>
Handler<Fun, void(Args...)> makeHandlerInternal(events::EventQueue &eq, Fun fun, void (Fun::*)(Args...) const)
{
    return Handler<Fun, void(Args...)>(eq, fun);
}

template <class Fun, class... Args>
Handler<Fun, void(Args...)> makeHandler(events::EventQueue &eq, Fun fun)
{
    return Handler<Fun, void(Args...)>(eq, fun, &Fun::operator());
}

因为 Args... 似乎在我可以将其定义为 Fun::operator() 采用的参数之前被推断为一个空列表。

附带说明:是否有一个非常好的资源来加强模板编程技能以解决此类问题?

【问题讨论】:

  • 我认为您的主模板不正确:它应该只是 template &lt;class&gt; class Handler; 而不是 template &lt;class, class&gt; class Handler;
  • 特化还有一个未使用的参数class Fun
  • 关于模板的好书:C++ 模板:完整指南(D.Vandevoorde,N.Josuttis),C++ 模板元编程(D.Abrahams , A.Gurtovoy),高级 C++ 元编程 (D.Di Gennaro),现代 C++ 设计 (A.Alexandrescu)。

标签: c++ templates lambda closures


【解决方案1】:

您的语法几乎是正确的。我认为没有充分的理由将 Args... 转换为 void(Args...) 并建议以下解决方案:

// This stub is here for the sake of code completeness only
class EventQueue {
public:
    template<class T, class Fun, class... Args>
    void call(T* obj, Fun fun, Args... args) const {
        std::invoke(fun, obj, args...);
    }
};

template<class Fun, class... Args>
class Handler {
public:
    Handler(EventQueue& eq, Fun fun) : eq_(eq), fun_(fun) {}

    void operator()(Args... args) {
        eq_.call(this, &Handler::callInternal, args...);
    }

private:
    EventQueue& eq_;
    Fun fun_;

    void callInternal(Args... args) {
        fun_(args...);
    }
};

template<class Fun, class... Args>
auto makeHandlerImpl(EventQueue& eq, Fun fun, void (Fun::*)(Args...) const) {
    return Handler<Fun, Args...>(eq, fun);
}

template<class Fun>
auto makeHandler(EventQueue& eq, Fun fun) { 
    return makeHandlerImpl(eq, fun, &Fun::operator());
}

EventQueue eq;
auto handler = makeHandler(eq, [](int result) { std::cout << result; });
handler(2020); // prints 2020

或者,您可以从 Handler 类型中删除 Args...,但将 operator() 设为模板,得到如下内容:

template<class Fun>
class Handler {
public:
    Handler(EventQueue& eq, Fun fun) : eq_(eq), fun_(fun) {}

    template<class... Args>
    void operator()(Args... args) {
        // static_assert(std::is_invocable_v<Fun, Args...>);
        eq_.call(this, &Handler::callInternal<Args...>, args...);
    }

private:
    EventQueue& eq_;
    Fun fun_;

    template<class... Args>
    void callInternal(Args... args) {
        fun_(args...);
    }
};

现在,使用 C++17 CTAD,您可以简单地编写:

EventQueue eq;
Handler handler(eq, [](int result) { std::cout << result; });
handler(2020);  // prints 2020

没有任何特殊的辅助函数。

【讨论】:

  • 这成功了。我试图将 Handler 修复为 void(Args...) 的原因是 EventQueue::call 将回调发布到队列中,以便稍后调用回调。那时返回一个值是无意义的,因为没有地方可以返回该值。但我现在接受了你的第二个建议以使其发挥作用。稍后我可能会使用 static_assert 来检查传入模板类型的特征
猜你喜欢
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 2011-12-27
  • 2021-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-16
相关资源
最近更新 更多