Yes, it does clarify. And it also gives an opportunity to point out that
your design has a few pitfalls you need to look out for.
The first one is that overload resolution is done on the *static* (or
declared) type. In order to be able to call the correct overload of the
processing functions, if you only have a pointer to the base class
available, you need a double-dispatch mechanism.
See the output from this program for what I mean:
#include <iostream>
using namespace std;
struct MessageA {};
struct MessageB {};
class MessageBase;
template <class T> class Message;
void Process(const MessageBase*);
void Process(const Message<MessageA>*);
class MessageBase
{
public:
virtual ~MessageBase() {}
virtual void call_Process() = 0;
};
template <class T>
class Message : public MessageBase, public T
{
Message() : MessageBase(), T() {}
public:
virtual void call_Process()
{
Process(this);
}
static Message<T>* create() { return new Message<T>(); }
};
void Process(const MessageBase*)
{
cout << " Process(const MessageBase*)" << endl;
}
void Process(const Message<MessageA>*)
{
cout << " Process(const Message<MessageA>*)" << endl;
}
int main()
{
MessageBase* message1 = Message<MessageA>::create();
MessageBase* message2 = Message<MessageB>::create();
cout << "Calling \"Process\" directly" << endl;
Process(message1);
Process(message2);
cout << "Calling \"Process\" indirectly" << endl;
message1->call_Process();
message2->call_Process();
}
Bart v Ingen Schenau
Yes, it does clarify. And it also gives an opportunity to point out that
your design has a few pitfalls you need to look out for.
The first one is that overload resolution is done on the *static* (or
declared) type. In order to be able to call the correct overload of the
processing functions, if you only have a pointer to the base class
available, you need a double-dispatch mechanism.
See the output from this program for what I mean:
#include <iostream>
using namespace std;
struct MessageA {};
struct MessageB {};
class MessageBase;
template <class T> class Message;
void Process(const MessageBase*);
void Process(const Message<MessageA>*);
class MessageBase
{
public:
virtual ~MessageBase() {}
virtual void call_Process() = 0;
};
template <class T>
class Message : public MessageBase, public T
{
Message() : MessageBase(), T() {}
public:
virtual void call_Process()
{
Process(this);
}
static Message<T>* create() { return new Message<T>(); }
};
void Process(const MessageBase*)
{
cout << " Process(const MessageBase*)" << endl;
}
void Process(const Message<MessageA>*)
{
cout << " Process(const Message<MessageA>*)" << endl;
}
int main()
{
MessageBase* message1 = Message<MessageA>::create();
MessageBase* message2 = Message<MessageB>::create();
cout << "Calling \"Process\" directly" << endl;
Process(message1);
Process(message2);
cout << "Calling \"Process\" indirectly" << endl;
message1->call_Process();
message2->call_Process();
}
Bart v Ingen Schenau
Excellent point! I had assumed some sort of special processing would
be needed in the handlers to get to the correct overload, but this
illustrates the point quite clearly. I didn't think about overload
resolution being based on the static type... This may throw my design
out the window altogether.
As a side note, I ended up abandoning templates for the derived
classes. It turns out each and every message type would need its own
specialization anyway which kinda defeats the point of the templates.
So now I have 20 some odd similar yet different message classes.
I want this, but I don't think it can work (forgive any typos as I
don't have a compiler on this machine):
struct LegacyMessageA {};
class MessageBase
{
public:
virtual ~MessageBase() {}
};
class MessageA : public MessageBase
{
public:
MessageA(const MessageA msg) : MessageBase() //assume msg is copied
to m_msg
// assume accessors into m_msg
// assume common functionality for all messages
private:
LegacyMessageA m_msg
};
class HandlerBase
{
public:
HandlerBase(){}
virtual ~HandlerBase(){}
void process( MessageBase* msg );
protected:
void _doProcess( MessageBase* msg );
};
HandlerBase:

rocess( MessageBase* msg )
{
// this is only ever going to call _doProcess( MessageBase* msg )
// as opposed to the proper overload for the actual type of msg.
// I want it to call the proper overload in the derived handler!
this->_doProcess( msg )
}
HandlerBase::_doProcess( MessageBase* msg )
{
//do nothing
}
class MessageAHandler : HandlerBase
{
public:
DerivedHandler() : HandlerBase() {}
private:
_doProcess( MessageA* msg );
};
MessageAHandler::_doProcess( MessageA* msg )
{
// special handling for type MessageA.
// I don't think this ever gets called as all messages go
// to MessageBase::_doProcess( MessageBase* msg )
}
int main()
{
LegacyMessageA msg;
MessageBase* message1 = new MessageA( msg );
HandlerBase* handler = new MessageAHandler();
handler->process( message1 );
delete message1;
delete handler;
return 0;
}
Can this scheme work at all? It all seemed so elegant yesterday!