TITLE: Run-time type information and messages PROBLEM: reindorf@us-es.sel.de (Charles Reindorf) Here is a (probably) well-worn sort of example of the need for RTTI One feasible example which comes to mind is the design of a system modelled as a set of objects communicating asynchronously in the same process. [...] RESPONSE: rmartin@uunet.uu.net!rcm (Robert Martin), 6 May 93 Charles goes on to describe a message passing scheme in which the recipient of the message is an object derived from the following class: class AsyncObject { void Receive(Message*) = 0; }; Users derive their own kinds of AsyncObject and their own kinds of messages. For example: class MyAsyncObject : public AsyncObject {...}; class MyMessage : public Message {...}; The message passing system can be modeled as in the following "Send" function.... void Send(AsyncObject& a, Message* m) { // somehow enter thread of a. a.Receive(m) }; Clearly this function only hints at the multi-threaded nature that Charles is after, but it does illustrate the "type" semantics that he was describing. Now. Clearly MyAsyncObject wants to receive instances of MyObject. But it inherits only Receive(Message*). Thus the body of the Receive function within MyAsyncObject must downcast the Message: void MyAsyncObject::Receive(Message* m) { MyMessage* mm = (MyMessage*)m; // do something with mm... } This is ugly :( However things get much worse if we put a "type" code inside Message... class Message { public: Message(int theType) : itsType(theType) {} int GetType() const {return itsType;} private: int itsType; }; Now, senders may decide to try to couple certain derivatives of Message with certain 'type' codes. This leads to ugly switch statements with "blind" downcasts within them... enum MessageType {zip, zop}; class ZipMessage : public Message { public: ZipMessage() : Message(zip) {} }; class ZopMessage : public Message { public: ZopMessage() : Message(zop) {} }; void MyAsyncObject::Receive(Message* m) { switch(m->GetType()) { case zip: ZipMessage* zipper = (ZipMessage*)m; .... break; case zop: ZopMessage* zopper = (ZopMessage*)m; .... break; } } And so the system grows uglier and uglier. There is a better way... (See the keeper of the records. Alaine of Lyndar. (private joke)). How can one do this without the need for downcasts and switch statements and other forms of ugliness???? Instead of giving AsyncObject the ability to "Recieve" we give Message the ability to "Perform...." class Message { public: void Perform() const = 0; }; The "Send" function obeys a model like this: void Send(AsyncObject& a, Message* m) { // somehow enter thread of 'a'. m->Perform(); } Messages are derived like this... class MyMessage : public Message { public: MyMessage(MyAsyncObject& a) itsReceiver(a) {}; void Process() const {itsReciever.SomeFunction();} private: MyAsyncObject itsReceiver; }; And the receiving object looks like this: class MyAsyncObject : public AsycnObject { public: void SomeFunction(); // gets executed when MyMessage is sent. }; This gets rid of the need for downcasting and switch statements in a message passing scheme. It also allows for a much more interesting paradigm for concurrent objects, since it would be possible for all communication between objects to be in the form of member functions. When a change in "thread" had to occur, an object would simply send itself a "Message". RESPONSE: maxtal@physics.su.OZ.AU (John Max Skaller), 16 May 93 [...] The basic problem that this does not solve is the problem which cannot be solved because it has no solution: how do you have an extensible set of AsynchObjects and also and extensible set of Messages, and have the messages interact with the objects depending on the exact type of *both*? The name for the general answer is 'multi-methods'. Multi-methods are inherently impossible: they simply cant exist in a static class based Object Oriented environment. You can have multi-methods, but only if you give up something else, for example, encapsulation, arbitrary extensibility of both sets, and probably other things would also do. The basic problem is that for two sets of types of size N and M you need N x M methods. Even if most of these methods are 'nop's. The problem is not that you cant do this: for any finite sets, N and M are fixed, and you can just do a dirty big double switch or indexed lookup into a matrix of methods. No, the problem is that you have nowhere to *specify* the methods: which class do you put the method *in*? That is, how do you provide *access* to the private internals safely of both sets? You cant, so you give up on protection, or you give up on multi-methods, or, you give up on arbitrary extension. Take you pick. My proposal for variants allows *statically* supported multi-methods. It gives up on extensibility (you have to recompile when you add a new case). The RTTI/downcasting solution is extensible, but gives up on protection and encapsulation. I think variants should be prefered in C++ where possible, but its not always possible. Its especially hard to chose variants as the best option when they are not yet in the language :-)