无法推断可变参数模板的模板类型

时间:2014-10-24 13:28:37

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

我已经创建了基于回调的简单事件管理器并且它可以工作,但我有一些错误,模板参数为零。

class event_manager
{
public:
    template <typename... T>
    static void register_event(const unsigned long e, std::function<void(T...)> ec)
    {
        events.insert({ e, ec });
    }

    template <typename... T>
    static void fire_event(const unsigned long e, T... args)
    {
        for (auto it : events)
        {
            if (it.first == e)
            {
                boost::any_cast<std::function<void(T...)>>(it.second)(args...);
            }
        }
    }
private:
    static std::unordered_multimap<unsigned int, boost::any> events;
};

我正在使用此代码添加回调:

event_manager::register_event<unsigned int>(DVU_EVENT_KEY_PRESSED, [](unsigned int key)
{
    //Works!
});

event_manager::register_event(DVU_EVENT_IDLE, []()
{
    //Could not deduce template argument
});

第二个问题: 是否可以更改代码以删除<unsigned int> - 如模板规范?

示例:

event_manager::register_event(DVU_EVENT_KEY_PRESSED, [](unsigned int key){}));

3 个答案:

答案 0 :(得分:1)

由于lambda只是一个带有operator()的仿函数,你可能只是有一个重载,最终会推断它:

template <typename F>
static void register_event(const unsigned long e, F f) 
{
    register_event(e, f, &F::operator());
}

template <typename F, typename R, typename... T>
static void register_event(const unsigned long e, F& f, 
    R (F::*method)(T...) const) 
{
    std::function<R(T...)> func = f;
    events.insert({ e, func });
}

可能需要R == voidstatic_assert或其他。

答案 1 :(得分:1)

即使是第一个也没有像你一样编译here

std::function与lambda不完全匹配,并且当您使用可变参数模板时,您不能以这种方式指定所有类型(因为您指定了第一种类型,编译器可能会推断出其余类型)。 / p>

可能的解决方法是仅传递func

template <typename Func>
static void register_event(const unsigned long e, Func ec);

并使用Func::operator()

重建std :: function

答案 2 :(得分:0)

您的设计是不安全的,因为依靠类型推导来生成完全匹配的类型是脆弱的,并且您的演员需要完全匹配的类型。

这是一个略有不同的设计:

class event_manager {
public:
  template <typename Signature>
  static void register_event(const unsigned long e, std::function<Signature> ec) {
    events.emplace( e, std::move(ec) );
  }

  template <typename Signature, typename...Ts>
  static void fire_event(const unsigned long e, Ts...&& args) {
    auto r = events.equal_range( e );
    for (auto it = r.first; it != r.second; ++it)
    {
      auto&& f = boost::any_cast<std::function<Signature> const&>(it.second);
      f(std::forward<Ts>(args)...);
    }
  }
private:
  static std::unordered_multimap<unsigned int, boost::any> events;
};

这里传递两端的函数签名,如void()void(int)。这些类型必须完全匹配。

我完全将fire_event的参数转发给我从地图中拉出来的函数。

我做了一些其他改进,比如正确移动/放置并删除了一些虚假副本。

出于某些原因,推断lambda的签名是一个坏主意。首先,因为C ++ 14 auto lambdas即将到来。其次,它意味着lambda或函数需要T const&T或其他任何&#34;泄漏&#34;如何调用它(你的原始实现要求所有值都按值进行)。

现在,您在注册它的位置以及触发它的位置都会明确列出给定事件的签名。如果它不匹配,应该更容易注意到。

我也会被any_cast指向/从指针移动,并声明它是非空的,而不是在你得到签名错误时抛出。