【问题标题】:Allow multiple signatures for lambda/callback function as template parameter允许 lambda/回调函数的多个签名作为模板参数
【发布时间】:2017-11-04 17:30:01
【问题描述】:

我想允许其类型被指定为模板参数的可调用对象有多个签名。更具体地说,我有一个模板化的update 方法,它接受一个必须返回float 的可调用对象,并使用它来更新数据网格中的值。对此的简化说明是

template <typename Fn>
void update(Fn&& fn_)
{
    for (Vec3 pos : m_grid)
    {
        float val = fn_(pos, m_grid)
        m_grid(pos) = val;
        ...

在上述情况下,fn_ 的签名必须始终将 pos 和 grid 作为参数,即使它们在实现中被忽略

我想使用一些模板魔法来允许回调签名的多种排列。

特别是,我希望允许采用pos 但不采用grid 的回调,以及根本不采用任何参数的回调。我不介意参数的顺序是否被强制执行。

任何人有任何关于如何做到这一点的提示吗?我不介意使用 boost 或其他库,但它们应该只是标题。

【问题讨论】:

    标签: c++ templates lambda c++14 variadic-templates


    【解决方案1】:

    您可以使用 SFINAE 使用 is_invocableC++17:std::is_invocable,或更早的 boost:boost::callable_traits::is_invocable)使用辅助函数来完成此操作

    template <typename Fn,
              std::enable_if_t<std::is_invocable<Fn, Vec3, Grid>::value>* = nullptr>
    float call_helper(Fn&& fn_, const Vec3& pos_, const Grid& grid_)
    {
      return fn_(pos_, grid_);
    }
    
    template <typename Fn,
              std::enable_if_t<std::is_invocable<Fn, Vec3>::value>* = nullptr>
    float call_helper(Fn&& fn_, const Vec3& pos_, const Grid& grid_)
    {
      return fn_(pos_);
    }
    
    template <typename Fn,
              std::enable_if_t<std::is_invocable<Fn, Grid>::value>* = nullptr>
    float call_helper(Fn&& fn_, const Vec3& pos_, const Grid& grid_)
    {
      return fn_(grid_);
    }
    
    template <typename Fn>
    void update(Fn&& fn_)
    {
        for (Vec3 pos : m_grid)
        {
            float val = call_helper(fn_, pos, m_grid)
            m_grid(pos) = val;
            ...
    

    【讨论】:

    • 单个仿函数可能支持多个这些调用,在这种情况下,您将得到一个模棱两可的重载错误。在这种情况下,也许这就是您想要的。如果没有,还有一些方法可以优先考虑重载(比如,如果可以使用两个参数调用它,那么它应该“获胜”)。
    • 这是一个艰难的决定,但我认为我最喜欢(至少,可以理解)这个解决方案。我的一个担忧是额外的函数调用是否会被编译掉,即它是否会产生任何额外的开销?
    • 似乎callable_traits 还没有作为 boost 的一部分发布,它有“Boost 1.66(2017 年 12 月)的预期版本”(github.com/badair/callable_traits)。不幸的是,但不会破坏交易。
    • 我设法用std c++14 特征做到了这一点,类似于enable_if_t&lt;is_same&lt;result_of_t&lt;Fn(Vec3&amp;, Grid&amp;), float&gt;&gt;::value, float&gt;。它不像上面那样整洁,但效果很好。我相当有信心额外的函数调用将被优化掉,尽管我想对此进行一些确认。无论如何,我会接受这个作为答案。
    【解决方案2】:

    特别是,我希望允许采用 pos 但不采用 grid 的回调,以及根本不采用任何参数的回调。

    只需定义两个重载,然后使用 lambdas 将请求转发到 complete 函数,从而过滤额外的参数。
    作为一个最小的工作示例:

    struct S {
        template <typename Fn>
        auto update(Fn &&fn_)
        -> decltype(fn_(0, 0), void())
        {
            // ...
            fn_(0, 0);
            // ...
        }
    
        template <typename Fn>
        auto update(Fn &&fn_)
        -> decltype(fn_(0), void())
        { update([&fn_](auto a, auto) { fn_(a); }); }
    
        template <typename Fn>
        auto update(Fn &&fn_)
        -> decltype(fn_(), void())
        { update([&fn_](auto, auto) { fn_(); }); }
    };
    
    int main() {
        S s;
        s.update([](auto, auto) {});
        s.update([](auto) {});
        s.update([]() {});
    }
    

    【讨论】:

    • 通过使用0 作为测试参数,您假设该类型可转换为零,例如指针?由于参数不是指针,所以我认为此解决方案需要使用虚拟 Vec3Grid 参数代替 0s?
    • 我使用ints 作为参数,因为这是一个最小的示例,但是只要声明的参数数量不同,您就可以随意使用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多