How can I create a type based lookup table in order to implement multiple-dispatch in C++?
-
22-07-2019 - |
Question
I'm attempting to make a messaging system in which any class derived from "Messageable" can receive messages based on how the function handleMessage() is overloaded. For example:
class Messageable {
public:
void takeMessage(Message& message) {
this->dispatchMessage(message);
}
protected:
void bindFunction(std::type_info type, /* Need help here */ func) {
m_handlers[type] = func;
}
void dispatchMessage(Message& message) {
m_handlers[typeid(message)](message);
}
private:
std::map<std::type_info, /*Need help here*/ > m_handlers;
};
class TestMessageable : public Messageable {
public:
TestMessageable() {
this->bindFunction(
typeid(VisualMessage),
void (TestMessageable::*handleMessage)(VisualMessage));
this->bindFunction(
typeid(DanceMessage),
void (TestMessageable::*handleMessage)(DanceMessage));
}
protected:
void handleMessage(VisualMessage visualMessage) {
//Do something here with visualMessage
}
void handleMessage(DanceMessage danceMessage) {
//Do something here with danceMessage
}
};
In a nutshell I want the correct version of handleMessage to be called based on the RTTI value of any given message.
How can I implement this preferably without some sort of monolithic switch/case statement.
Solution
You should look into the Double Dispatch pattern. See information here.
You should be able to implement VisualMessage as a class like such:
class VisualMessage : public Message
{
public:
virtual void dispatch(Messageable & inMessageable)
{
inMessageable.handleMessage(*this);
}
};
and then call it like this:
Message & vMessage = VisualMessage();
Messageable & tMessageable = TestMessageable();
vMessage.dispatch(tMessageable);
It will then call TestMessageable::handleMessage(VisualMessage & visualMessage)
This is because Message::dispatch will be based on the VisualMessage type. Then when VisualMessage::dispatch calls inMessageable.handleMessage(*this) it will call the right handleMessage because the type of the *this pointer is VisualMessage, not Message.
OTHER TIPS
To fix your code:
struct CompareTypeInfo
: std::binary_function<const std::type_info*, const std::type_info*, bool>
{
bool operator()(const std::type_info* a, const std::type_info* b) {
return a->before(*b);
}
};
class Messageable
{
protected:
typedef void (*handlefn)(Messageable *, Message &);
void bindFunction(const std::type_info& type, handlefn func) {
m_handlers[&type] = func;
}
void dispatchMessage(Message& message) {
m_handlers[&typeid(message)](this, message);
}
template <typename S, typename T>
static void handle(Messageable *self, Message &m) {
static_cast<S*>(self)->handleMessage(static_cast<T&>(m));
}
private:
std::map<const std::type_info*, handlefn, CompareTypeInfo> m_handlers;
};
class TestMessageable : public Messageable
{
public:
TestMessageable()
{
this->bindFunction(
typeid(VisualMessage), &Messageable::handle<TestMessageable,VisualMessage>);
this->bindFunction(
typeid(DanceMessage), &Messageable::handle<TestMessageable,DanceMessage>);
}
public:
void handleMessage(VisualMessage visualMessage)
{
//Do something here with visualMessage
}
void handleMessage(DanceMessage danceMessage)
{
//Do something here with danceMessage
}
}
};
Those static_casts could be dynamic_casts for "extra safety" (assuming there are virtual functions kicking around). But the design means you know self must be a pointer to S, because otherwise it wouldn't have this function registered to it, and you know m must refer to a T, because its typeid has already been checked in dispatchMessage. So a failed cast can't happen if the class is used correctly, and all you can do if it does happen is debug.
Actually I think you could cut down the verbiage a bit more by making bindFunction a template too:
template <typename S, typename T>
void bindFunction(void)
{
m_handlers[&typeid(T)] = handle<S,T>;
}
Then call it with:
this->bindFunction<TestMessageable,VisualMessage>();
But still, you can see why Steve Rowe's double dispatch code is usually preferred...
This is an old question but the NUClear library is designed to provide fast and type-safe message passing in a similar vein to the original intent of this question.
Full Disclosure: I am one of the co-developers of NUClear
In this case the TestMessageable
class is implemented as a NUClear::Reactor
like so:
#include <NUClear.h>
// TestMessageable.h
class TestMessageable : NUClear::Reactor {
public:
TestMessageable(NUClear::PowerPlant* powerPlant);
private:
};
// TestMessageable.cpp
#include "TestMessageable.h"
TestMessageable::TestMessageable(NUClear::PowerPlant* powerPlant)
: NUClear::Reactor(powerPlant) {
on<Trigger<VisualMessage>>([this](const VisualMessage& message) {
// Do something with VisualMessage here
// On can also take anything that is callable with a const& VisualMessage.
// Messages are sent using emit.
// If you don't have C++14 NUClear provides std::make_unique
auto classifiedData = std::make_unique<ClassifiedVision>(/* stuff */);
emit(std::move(classifieData));
});
on<Trigger<DanceMessage>>([this](const DanceMessage& message) {
// Do something with DanceMessage here.
});
}
You will find such kind of implementation in Scott Meyers' More Effective C++ and item - 31 is what you want & nicely explained.