简体   繁体   中英

Could not deduce template type for variadic template

I have created simple callback based event manager and it works, but I have some errors with zero template arguments.

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

And I'm using this code to add callback:

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

And the second question: Is it possible to change code to remove <unsigned int> -like template specification?

Example:

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

Since a lambda is just a functor with operator() , you could just have an overload that ends up deducing it:

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

Maybe either require that R == void or static_assert it or something.

Even the first one doesn't compile here as you .

std::function is not an exact match for the lambda, and as you use variadic template you can't specify all type that way (as you specify the first type and compiler may deduce the rest).

A possible workaround is to pass just the func

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

and reconstruct the std::function with Func::operator()

Your design is unsafe, as relying on type deduction to produce exactly matching types is fragile, and your cast requires exactly matching types.

Here is a slightly different design:

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

here you pass in a function signature at both ends, like void() or void(int) . These types have to match exactly.

I perfect forward the arguments to fire_event to the function I pull out of the map.

I did some other improvements, like properly moving/emplacing and removed some spurious copies.

Deducing the signature of a lambda is a bad idea for a few reasons. First, because C++14 auto lambdas are coming. Second, it means the fact that a lambda or function takes a T const& or a T or whatever "leaks" into how you have to call it (your original implementation required all values be taken by-value).

The signature of a given event now is explicitly listed both where you register it, and where you fire it. If it doesn't match, it should be easier to notice.

I would also be tempted to any_cast to/from a pointer, and assert it is non-null, instead of throwing if you get the signature wrong.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM