无法推导可变模板的模板类型

Could not deduce template type for variadic template

本文关键字:类型      更新时间:2023-10-16

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

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

由于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之类的。

即使是第一个也不会像您一样在这里编译。

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

一个可能的解决方法是只通过函数

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

并用Func::operator()

重构std::函数

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

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

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 Lambda即将问世。其次,这意味着lambda或函数将T const&T或任何"泄漏"到您必须如何调用它的东西中(您的原始实现要求所有值都按值取值)。

给定事件的签名现在会明确列出在哪里注册和在哪里触发。如果不匹配,应该更容易注意到。

我也会尝试从指针any_cast,并断言它是非null的,而不是在签名错误时抛出。