14

This is a question with answer, the aim of which is to invite the readers to suggest their own solutions.
I'm quite sure that out there there are more clever approaches than mine, so I'd like to know what those solutions are.
Please, share your knowledge by adding your own answers!!


The goal is to create an emitter class that can be used to dispatch a few events.

An important feature I want to be in the emitter is an easy to use registration facility to attach the listeners to the emitter.

In other terms, I don't want to write functions/methods aimed to attach all the listeners to the emitter, for it could be error prone and I found myself more than once looking for a bug that was due to a missed line of code because of that (a line that would have registered the N-th listener, of course).

Imagine the following structs:

struct E1 { };

struct S {
    void receive(const E1 &ev) { /* do something */ }
};

The registration facility I'm looking for is such a solution for which the following lines of code are enough:

S s;
emitter.reg(s);

And that's all, even if in the future it arises the requirement for one more listener to be added to the struct S, as an example:

struct E1 { };
struct E2 { };

struct S {
    void receive(const E1 &ev) { /* do something */ }
    void receive(const E2 &ev) { /* do something */ }
};

How could I write such an emitter?

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 1
    Er... what do you want `emitter.reg(s)` to actually do? And what is `emitter`, anyway? Is it an instance of a class template? Do you know all of the `E1`, `E2`, ... types in advance? – Barry Apr 19 '16 at 19:23
  • More or less. I must know them if I want to have a definition like `Emitter`. Class template? Maybe, it depends on the actual implementation, that is what I proposed as an answer. – skypjack Apr 19 '16 at 19:27
  • I wrote a small library that addresses a very similar problem. The tldr is to use type-erasure on the subscriber type and index the type-erased subscribers by std::type_index. The lib is https://github.com/mmcshane/eventbus . I'm posting as a comment instead of an answer to avoid the appearance of self-promotion. – mpm Apr 27 '16 at 15:41
  • What's wrong with Qt's signal/slot implementation? ... Apart from the fact that they hijacked the langauge. – Trevor Hickey Apr 28 '16 at 01:39

3 Answers3

7

First the includes:

#include <iostream>
#include <vector>
#include <type_traits>
#include <utility>
#include <functional>

We use the void_t detection helper:

template<class ...>
using void_t = void;

We define a trait for detecting the receive() methods using void_t:

template<class C, class E, class X = void_t<>>
struct has_event_handler :
      std::false_type {};

template<class C, class E>
struct has_event_handler<C, E, void_t< decltype(
    std::declval<C>().receive(std::declval<const E>())
) >> : std::true_type {};

template<class C, class E>
constexpr bool has_event_handler_v = has_event_handler<C, E>::value;

Using this, we can define the emitter class. The variadic arguments are the type of events it can manage:

template<class...> class Emitter;

// Recursive case:
template<class E, class... F>
class Emitter<E, F...> : Emitter<F...> {
public:
  // Register:
  template<class C>
  std::enable_if_t<!has_event_handler_v<C,E>> reg(C& callback) {
    Emitter<F...>::reg(callback);
  };
  template<class C>
  std::enable_if_t<has_event_handler_v<C,E>> reg(C& callback) {
    handlers_.push_back([&callback](E const& event) { return callback.receive(event); });
    Emitter<F...>::reg(callback);
  };
  void trigger(E const& event)
  {
    for (auto const& handler : handlers_)
      handler(event);    
  }
  template<class G>
  void trigger(G const& event)
  {
    Emitter<F...>::trigger(event);
  }
private:
  std::vector<std::function<void(const E&)>> handlers_;
};

// Base case:
template<>
class Emitter<> {
public:
  template<class C>
  void reg(C& callback) {};
  template<class E>
  void trigger(E const& event)
  {
     static_assert(!std::is_same<E,E>::value,
       "Does not handle this type of event.");
  }
};

For the trigger() part, another solution would be to use std::enable_if_t<std::is_base_of_v<E, G>>.

And we can use it with:

// Events
struct E1 {};
struct E2 {};
struct E3 {};

// Handler
struct handler {
  void receive(const E1&)
  {
    std::cerr << "E1\n";
  }
  void receive(const E2&)
  {
    std::cerr << "E2\n";
  }
};

// Check the trait:
static_assert(has_event_handler_v<handler, E1>, "E1");
static_assert(has_event_handler_v<handler, E2>, "E2");
static_assert(!has_event_handler_v<handler, E3>, "E3");

int main()
{
  Emitter<E1, E2> emitter;
  handler h;
  emitter.reg(h);
  emitter.trigger(E1());
  emitter.trigger(E2());
}

Note: I used the _v and _t variants from C++17 in order to have a shorter code but for compatibility with C++11 you might want to use the struct versions (typename std::enable_if<foo>::type, std::is_base_of<B,D>::value, etc.).

Update: it's probably better to use composition instead of inheritance for the recursive case of Emitter:

template<class E, class... F>
class Emitter<E, F...> {
public:
  // Register:
  template<class C>
  std::enable_if_t<!has_event_handler_v<C,E>> reg(C& callback) {
    Emitter<F...>::reg(callback);
  };
  template<class C>
  std::enable_if_t<has_event_handler<C,E>::value> reg(C& callback) {
    handlers_.push_back([&callback](E const& event) { return callback.receive(event); });
    emitter_.reg(callback);
  };
  void trigger(E const& event)
  {
    for (auto const& handler : handlers_)
      handler(event);    
  }
  template<class G>
  void trigger(G const& event)
  {
    emitter_.trigger(event);
  }
private:
  std::vector<std::function<void(const E&)>> handlers_;
  Emitter<F...> emitter_;
};
ysdx
  • 8,889
  • 1
  • 38
  • 51
3

My version without inheritance:

template <typename C, typename E> std::false_type has_event_handler_impl(...);
template <typename C, typename E>
auto has_event_handler_impl(int)
-> decltype(static_cast<void>(std::declval<C>().receive(std::declval<const E>())),
            std::true_type{});

template <typename C, typename E>
using has_event_handler = decltype(has_event_handler_impl<C, E>(0));

template <class... Es>
class Emitter {
public:

    template<class C>
    void reg(C& callback) {
        const int dummy[] = { 0, (regT<Es>(callback), 0)...};
        static_cast<void>(dummy); // Avoid unused variable warning
    }

    template <typename E>
    void emit(const E& event)
    {
        for (auto const& handler : get_vector<E>()) {
            handler(event);
        }
    }

private:
    template <typename E, typename C>
    std::enable_if_t<has_event_handler<C, E>::value>
    regT(C& callback)
    {
        auto lambda = [&callback](const E& event) { return callback.receive(event); };
        get_vector<E>().push_back(lambda);
    }

    template <typename E, typename C>
    std::enable_if_t<!has_event_handler<C, E>::value>
    regT(C&)
    {
        /* Empty */
    }

    template <typename E>
    std::vector<std::function<void(const E&)>>& get_vector()
    {
        return std::get<std::vector<std::function<void(const E&)>>>(handlers_);
    }

private:
    std::tuple<std::vector<std::function<void(const Es&)>>...> handlers_;
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

Here it follows a complete example of a possible emitter class that is strongly based on templates and sfinae.
One can compile it using the line g++ -g -std=c++14 main.cpp.
I tried to reduce the code to a minimal example, thus using naked pointers and only a few member methods.

#include <functional>
#include <vector>
#include <cassert>

template<class E>
struct ETag { using type = E; };

template<int N, int M>
struct Choice: public Choice<N+1, M> { };

template<int N>
struct Choice<N, N> { };

template<int S, class... T>
class Base;

template<int S, class E, class... O>
class Base<S, E, O...>: public Base<S, O...> {
    using OBase = Base<S, O...>;

protected:
    using OBase::get;
    using OBase::reg;

    std::vector<std::function<void(const E &)>>& get(ETag<E>) {
        return vec;
    }

    template<class C>
    auto reg(Choice<S-(sizeof...(O)+1), S>, C* ptr)
    -> decltype(std::declval<C>().receive(std::declval<E>())) {
        using M = void(C::*)(const E &);
        M m = &C::receive;
        std::function<void(const E &)> fn = std::bind(m, ptr, std::placeholders::_1);
        vec.emplace_back(fn);
        OBase::reg(Choice<S-sizeof...(O), S>{}, ptr);
    }

private:
    std::vector<std::function<void(const E &)>> vec;
};

template<int S>
class Base<S> {
protected:
    virtual ~Base() { }
    void get();
    void reg(Choice<S, S>, void*) { }
};

template<class... T>
class Emitter: public Base<sizeof...(T), T...> {
    using EBase = Base<sizeof...(T), T...>;

public:
    template<class C>
    void reg(C *ptr) {
        EBase::reg(Choice<0, sizeof...(T)>{}, ptr);
    }

    template<class E, class... A>
    void emit(A&&... args) {
        auto &vec = EBase::get(ETag<E>{});
        E e(std::forward<A>(args)...);
        for(auto &&fn: vec) fn(e);
    }
};

struct E1 { };
struct E2 { };
struct E3 { };

struct S {
    void receive(const E1 &) { e1 = !e1; }
    void reject(const E2 &) { e2 = !e2; }
    void receive(const E3 &) { e3 = !e3; }
    void check() { assert(e1); assert(e2); assert(e3); }
    bool e1{false};
    bool e2{true};
    bool e3{false};
};

int main() {
    S s;
    Emitter<E1, E2, E3> emitter;

    emitter.reg(&s);
    emitter.emit<E1>();
    emitter.emit<E2>();
    emitter.emit<E3>();
    s.check();
} 
skypjack
  • 49,335
  • 19
  • 95
  • 187