Событие-эмиттер и автоматическая регистрация методов-членов как слушателей

Это вопрос с ответом, цель которого - предложить читателям предложить свои собственные решения.
Я совершенно уверен, что там есть более умные подходы, чем у меня, поэтому я хотел бы знать, что это за решения.
Пожалуйста, поделитесь своими знаниями, добавив свои собственные ответы!!


Цель состоит в том, чтобы создать класс эмиттера, который можно использовать для отправки нескольких событий.

Важная функция, которую я хочу быть в эмиттере, - это простая в использовании функция регистрации, позволяющая подключить слушателей к эмиттеру.

Другими словами, я не хочу писать функции/методы, предназначенные для подключения всех слушателей к эмиттеру, поскольку это может быть подвержено ошибкам, и я обнаружил, что не раз искал ошибку, которая была вызвана пропущенной строка кода из-за этого (строка, которая, конечно, зарегистрировала бы N-го слушателя).

Представьте себе следующие структуры:

struct E1 { };

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

Объект регистрации, который я ищу, - это такое решение, для которого достаточно следующих строк кода:

S s;
emitter.reg(s);

И это все, даже если в будущем возникает требование добавления еще одного слушателя в struct S в качестве примера:

struct E1 { };
struct E2 { };

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

Как я могу написать такой эмиттер?

Ответ 1

Здесь следует полный пример возможного класса эмиттера, который сильно основан на шаблонах и sfinae.
Его можно скомпилировать, используя строку g++ -g -std=c++14 main.cpp.
Я попытался уменьшить код до минимального примера, используя при этом голые указатели и только несколько методов-членов.

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

Ответ 2

Сначала включает:

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

Мы используем void_t помощник обнаружения:

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

Определим признак для определения методов receive() с помощью 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;

Используя это, мы можем определить класс эмиттера. Вариадические аргументы - это тип событий, которые он может управлять:

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

Для части trigger() другим решением будет использовать std::enable_if_t<std::is_base_of_v<E, G>>.

И мы можем использовать его с помощью:

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

Примечание: Я использовал варианты _v и _t из С++ 17, чтобы иметь более короткий код, но для совместимости с С++ 11 вы можете использовать struct версии (typename std::enable_if<foo>::type, std::is_base_of<B,D>::value и т.д.).

Обновление:, вероятно, лучше использовать композицию вместо наследования для рекурсивного случая 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_;
};

Ответ 3

Моя версия без наследования:

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

Демо