Skip to content

Instantly share code, notes, and snippets.

@lsem
Last active July 24, 2022 16:17
Show Gist options
  • Save lsem/fd04266bf5c524b5373e686becefdb3c to your computer and use it in GitHub Desktop.
Save lsem/fd04266bf5c524b5373e686becefdb3c to your computer and use it in GitHub Desktop.
modern_message_bus_type_erased.cpp
#include <algorithm>
#include <any>
#include <cassert>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
template <class... Ts>
struct type_list {};
template <class L1, class L2>
struct type_list_cat;
template <class... L1, class... L2>
struct type_list_cat<type_list<L1...>, type_list<L2...>> {
using result = type_list<L1..., L2...>;
};
template <class L1, class L2>
using type_list_cat_t = typename type_list_cat<L1, L2>::result;
template <class... Ls>
struct type_list_cat_multiple;
template <class First, class... Rest>
struct type_list_cat_multiple<First, Rest...> {
using result = type_list_cat_t<First, typename type_list_cat_multiple<Rest...>::result>;
};
template <class Last1>
struct type_list_cat_multiple<Last1> {
using result = Last1;
};
template <class... Ls>
using type_list_cat_multiple_t = typename type_list_cat_multiple<Ls...>::result;
static_assert(std::is_same_v<type_list_cat_multiple_t<type_list<int, float>>, type_list<int, float>> );
static_assert(std::is_same_v<type_list_cat_multiple_t<type_list<int, float>, type_list<char, int>>, type_list<int, float, char, int>> );
template <class T>
struct spell_type;
template <class Msg>
class type_erased_handler {
private:
template <class M>
class base {
public:
virtual ~base() = default;
virtual void operator()(const M&) = 0;
};
template <class T, class M>
class derived : public base<M> {
std::shared_ptr<T> t;
public:
derived(std::shared_ptr<T> t) : t(std::move(t)) {}
virtual void operator()(const M& m) override { (*t)(m); }
};
public:
template <class T>
explicit type_erased_handler(std::shared_ptr<T> t)
: m_base_ptr(std::make_shared<derived<T, Msg>>(t)) {}
void operator()(const Msg& m) { (*m_base_ptr)(m); }
private:
std::shared_ptr<base<Msg>> m_base_ptr;
};
using typeid_t = std::string;
template <class T>
struct typeid_for;
#define generate_typeid_for(X) \
template <> \
struct typeid_for<X> { \
static std::string get() { return #X; } \
};
template <class... Msgs>
struct message_bus_t {
template <class Msg, class H>
void register_one(Msg, std::shared_ptr<H> h_ptr) {
// TODO: static assert that Msg in Msgs.
if constexpr (std::is_invocable_v<H, Msg>) {
auto h_typeid = typeid_for<Msg>::get();
type_erased_handler<Msg> wrapped_h(h_ptr);
m_handlers[h_typeid].push_back(wrapped_h);
} else {
auto h_typeid = typeid_for<Msg>::get();
}
}
template <class H>
void register_handler(std::shared_ptr<H> h) {
(register_one(Msgs{}, h), ...);
}
template <class Msg>
void raise(const Msg& m) const {
// TODO: static assert that Msg in Msgs.
auto h_typeid = typeid_for<Msg>::get();
// std::cout << "raising, type id: " << h_typeid << "\n";
if (auto it = m_handlers.find(h_typeid); it != m_handlers.end()) {
for (auto& h : it->second) {
auto h_casted = std::any_cast<type_erased_handler<Msg>>(h);
h_casted(m);
}
}
}
std::map<typeid_t, std::list<std::any>> m_handlers;
};
template <class... Msgs>
auto make_message_bus_for_types(type_list<Msgs...>) {
return message_bus_t<Msgs...>();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// END OF MAGIC, NOW USAGE:
// messages_core.h
namespace messages::core {
struct connection_added {};
struct connected {};
struct disconnected {};
using exported = type_list<connection_added, connected, disconnected>;
} // namespace messages::core
generate_typeid_for(messages::core::connection_added);
generate_typeid_for(messages::core::connected);
generate_typeid_for(messages::core::disconnected);
namespace messages::statistics {
struct message1 {};
struct message2 {};
using exported = type_list<message1, message2>;
} // namespace messages::statistics
generate_typeid_for(messages::statistics::message1);
generate_typeid_for(messages::statistics::message2);
// some 3rd party handler which combines both statistics and core features.
class statistics_and_core_handler {
public:
void operator()(const messages::core::connected&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
void operator()(const messages::statistics::message1&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
};
class core_messages_handler {
public:
void operator()(const messages::core::connection_added& m) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
void operator()(const messages::core::connected& m) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
void operator()(const messages::core::disconnected& m) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
};
// Example of handler in functional style.
template <typename... Ts>
struct overload : Ts... {
using Ts::operator()...;
};
template <class... Ts>
overload(Ts...) -> overload<Ts...>;
auto functional_handler() {
return overload{[](const messages::core::connected&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
},
[](const messages::statistics::message1&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}};
}
using all_messages =
type_list_cat_t<messages::core::exported, messages::statistics::exported>;
int main() {
auto bus = make_message_bus_for_types(all_messages{});
// spell_type<all_messages> _all_messages;
auto h1 = std::make_shared<core_messages_handler>();
auto h2 = std::make_shared<statistics_and_core_handler>();
auto h3_obj = functional_handler();
auto h3 = std::make_shared<decltype(h3_obj)>(std::move(h3_obj));
bus.register_handler(h1);
bus.register_handler(h2);
bus.register_handler(h3);
bus.raise(messages::core::disconnected{});
bus.raise(messages::core::connected{});
bus.raise(messages::statistics::message1{});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment