I'm trying to write a messaging system with templates, the visitor pattern and with the help of CRTPs. I understand these concepts but still, I'm in a situation where I have to retrive a "lost" type. I have a Base
class and I want to find a Derived<T>
. That's "two" types to deduce [Derived
could be anything, T
could be anything] (even if it's considered as one type).
I tried to make use of a second visitor pattern, which seems heavy and crazy but I did not find any working solution. Even if it's game related, it is just as an example, it could be applied to other programs I suppose, I can't expose it in another context.
Here is the naming I used (with unnecessary examples):
SubscriberBase
is a class that sends and receives messages (like a network client)Broadcaster
is the bridge between subscribers (like a network switch / server) and contains a vector of SubscriberBase
.I came up with this minimal code:
class SubscriberBase {};
class Broadcaster {
std::vector<SubscriberBase*> subscribers;
public:
template<typename TMessage>
void broadcast(TMessage& message) {
for(auto subscriber : subscribers) {
// <<< Here is my problem <<<
message.accept<THE_ACTUAL_SUBSCRIBER_TYPE>(subscriber);
}
}
void attach(SubscriberBase* subscriber) {
subscribers.push_back(subscriber);
}
};
//Base class for handling messages of any type
template<typename TMessage>
class MessageHandler {
public:
virtual void handleMessage(TMessage& message) {}
};
//Base class for messages
template<typename TMessage>
class Message {
friend class Broadcaster;
private:
//Visitor pattern with CRTP
template<typename TSubscriber>
void accept(TSubscriber* subscriber) {
subscriber->handleMessage(*static_cast<TMessage*>(this));
}
};
//Messages
struct EntityCreated : public Message<EntityCreated> {};
struct EntityDestroyed : public Message<EntityDestroyed> {};
struct BurnAllGummyBears : public Message<BurnAllGummyBears> {};
//Subscribers
class EntityCache : public SubscriberBase,
public MessageHandler<EntityCreated>,
public MessageHandler<EntityDestroyed>,
public MessageHandler<BurnAllGummyBears>
{
public:
void handleMessage(EntityCreated& message) override { /* add to cache */ }
void handleMessage(EntityDestroyed& message) override { /* remove from cache */ }
//does not override BurnAllGummyBears because it's not relevant for EntityCache
};
The problem is the type THE_ACTUAL_SUBSCRIBER_TYPE
. It could be any "concrete" subscriber; in this case it would be for example EntityCache
or something else like Logger
, CommandRecorder
...
I tried to use another CRTP coupled with another class:
class SubscriberBase {};
template<typename TSubscriber>
class Subscriber : public SubscriberBase { /* some other visitor pattern ? */ };
class EntityCache : public Subscriber<EntityCache>, /* ... */
with no success.
All ideas are appreciated, thank you :)
I gave up on this idea and thought "Well, KISS my .." so I decided to go for a simpler design, the normal and clean approach for a normal and clean code.
//Messages
struct EntityCreated {};
struct EntityDestroyed {};
struct ChopAllTrees {};
struct MakeGummyBearsEvil {};
//Subscriber is now a base class containing all of the message handlers
class Subscriber {
public:
virtual void handleMessage(EntityCreated& msg) {}
virtual void handleMessage(EntityDestroyed& msg) {}
virtual void handleMessage(ChopAllTrees& msg) {}
virtual void handleMessage(MakeGummyBearsEvil& msg) {}
};
class Broadcaster {
std::vector<Subscriber*> subscribers;
public:
template<typename M>
void broadcast(M& msg) {
for(auto subscriber : subscribers) {
subscriber->handleMessage(msg);
}
}
template<typename M>
void broadcast(M&& msg) {
M owner(msg);
broadcast(owner);
}
void attach(Subscriber* subscriber) {
auto it = std::find(subscribers.begin(), subscribers.end(), subscriber);
if(it == subscribers.end()) {
subscribers.push_back(subscriber);
}
}
void detach(Subscriber* subscriber) {
auto it = std::find(subscribers.begin(), subscribers.end(), subscriber);
if(it != subscribers.end()) {
subscribers.erase(it);
}
}
};
//Subscribers simply inherits from Subscriber and overrides interesting handlers
class EntityCache : public Subscriber {
void handleMessage(EntityCreated& msg) override {
std::cout << "Handling creation\n";
}
void handleMessage(EntityDestroyed& msg) override {
std::cout << "Handling destruction\n";
}
};
class Eviler : public Subscriber {
void handleMessage(MakeGummyBearsEvil& msg) override {
std::cout << "Gummy bears are now evil!\n";
}
};
int main() {
EntityCache entityCache;
Eviler eviler;
Broadcaster broadcaster;
broadcaster.attach(&entityCache);
broadcaster.attach(&eviler);
EntityCreated entityCreated;
broadcaster.broadcast(entityCreated); //lvalue test
broadcaster.broadcast(MakeGummyBearsEvil()); //rvalue test
broadcaster.detach(&eviler);
broadcaster.broadcast(EntityDestroyed());
broadcaster.broadcast(MakeGummyBearsEvil()); //no effect
}
I'm accepting this answer since it solves the issue, but since I'm still interested on how to do it, if anyone encountered this issue in the past (probably not) and handled it, feel free to answer and I'll accept it.