Вопрос

I am trying to write a simple dispatcher, the user code can attach callbacks to it. Each event has a known signature, and the user code will need to call dispatch with the right number and argument types. This is managed by the variadic arguments. But, freestandingInt is not accepted, as the vector is not of the right type. How to make it generic?

Follows a minimal example

void freestanding() {
 std::cout << "freestanding" << std::endl;
}

void freestandingInt(int iArg) {
  std::cout << "freestandingInt " << iArg << std::endl;
}


struct Dispatcher {
 typedef struct Event_ {
   std::vector<std::function<void()> > listeners;
 } Event;

 template<class... Args>
 void dispatch(int eventNr, Args&&... args) {
   for (auto listener: events[eventNr].listeners) {
     std::function<void()> f(std::bind(listener, std::forward<Args>(args)...));
     f();
   }
 }

 std::map<int, Event> events;
};

int main (int argc, char **argv) {
  Dispatcher disp;
  disp.events[0].listeners.push_back(freestanding);
  disp.dispatch(0); // OK

  // error here
  //disp.events[1].listeners.push_back(freestandingInt);

}
Это было полезно?

Решение

Here is an approach based on making a std::multimap from the std::type_index of the function to a std::function of the appropriate type:

#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <typeindex>

void freestanding() {
  std::cout << "freestanding" << std::endl;
}

void freestandingInt(int iArg) {
  std::cout << "freestandingInt " << iArg << std::endl;
}

// Base class for all functions so that we can store all functions
// in a single container.
struct Function {
  virtual ~Function() { }
};

// Derived class template for functions with a particular signature.
template <typename T>
struct BasicFunction : Function {
  std::function<T> function;
  BasicFunction(std::function<T> function) : function(function) { }
};

// Generic container of listeners for any type of function
typedef std::multimap<std::type_index,std::unique_ptr<Function> > Listeners;

template <typename Func>
static void addListener(Listeners &listeners,Func &function)
{
  std::type_index index(typeid(Func));
  std::unique_ptr<Function>
    func_ptr(new BasicFunction<Func>(std::function<Func>(function)));
  listeners.insert(Listeners::value_type(index,std::move(func_ptr)));
}

template <typename... Args>
static void callListeners(const Listeners &listeners,Args&&... args)
{
  typedef void Func(typename std::remove_reference<Args>::type...);
  std::type_index index(typeid(Func));
  Listeners::const_iterator i = listeners.lower_bound(index);
  Listeners::const_iterator j = listeners.upper_bound(index);
  for (;i!=j; ++i) {
    const Function &f = *i->second;
    std::function<Func> func =
      static_cast<const BasicFunction<Func> &>(f).function;
    func(std::forward<Args>(args)...);
  }
}

struct Dispatcher {
  typedef struct Event_ {
    Listeners listeners;
  } Event;

  template<class... Args>
  void dispatch(int eventNr, Args&&... args) {
    callListeners(events[eventNr].listeners,std::forward<Args>(args)...);
  }

  std::map<int, Event> events;
};

int main (int argc, char **argv) {
  Dispatcher disp;
  addListener(disp.events[0].listeners,freestanding);
  addListener(disp.events[0].listeners,freestandingInt);
  disp.dispatch(0,5);
}

Output:

freestandingInt 5

Другие советы

Additional approach is using std::bind

void show_text(const string& t)
{
    cout << "TEXT: " << t << endl;
}

std::function <void ()> f = std::bind(show_text, "Bound function"); 
    /*notice that the signature of the std::function f is void() */

more on that in this amazing post: https://oopscenities.net/2012/02/24/c11-stdfunction-and-stdbind/

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top