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