7

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

}
Alex Darsonik
  • 597
  • 5
  • 18
  • Why is there only one kind of event? As a different question, how many signatures? Can the list be centralized? What kind of errors on an argument mismatch is ok? – Yakk - Adam Nevraumont Jun 02 '13 at 14:54
  • That's it, I have no hope of keeping just one, am I forced to use inheritance and store Event* in the map? – Alex Darsonik Jun 02 '13 at 14:56
  • `Event*` is pointless. I wouod use type erasure before that. If there is more than one event, there can be more than one dispatcher as well. Why not? – Yakk - Adam Nevraumont Jun 02 '13 at 14:58
  • I would like to leave total freedom on the signature, besides the void return type. There should probably be a compiler error on argument mismatch, once the first function is added to the listener collection. I'd better not centralize the list as I want to have the Events in a hierarchy with a pointer to the parent. – Alex Darsonik Jun 02 '13 at 15:01
  • Your last sentence makes no sense to me. Compiler errors based on previous runtime actions are impossible. I will repeat the question: why have one tyoe of dispatcher and one kind of event? Each event can have a type describing its signature, and each duspatcher manages one list of identically typed events. Dispatchers can be created at compile time based on which signatures are used. – Yakk - Adam Nevraumont Jun 02 '13 at 15:12

2 Answers2

8

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
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • I've used it, and I really like it. It's also a very good example of the use of multimap with type_index, thanks – Alex Darsonik Jun 07 '13 at 08:31
  • At the very minimum, `Function` needs a virtual destructor. And the whole idea of reconstructing the function type from argument type when the arguments are taken by forwarding reference makes very little sense. As a simple example, this code breaks on `int i = 0; disp.dispatch(0, i);`. – T.C. Sep 10 '15 at 05:17
  • @T.C.: Both good points. I've added the virtual destructor. I also changed the code to use `std::remove_reference` to make it handle the case you mentioned, although there are plenty of other cases that still won't work. Making it handle overload resolution equivalent to the standard rules would be quite tricky. – Vaughn Cato Sep 10 '15 at 07:10
0

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/

Arty McLabin
  • 83
  • 1
  • 10