TITLE: Thoughts on dynamic_cast and a mythical "narrow" cast ----------------------------------------------------------------------------- BS = bs@alice.att.com (Bjarne Stroustrup), AT&T Bell Laboratories, 7 Oct 1994 JP = jjpersch@mac.shell.com (Jeff J. Persch) ----------------------------------------------------------------------------- JP: Wouldn't a real narrow operation be useful in the new RTTI extensions? BS: Hmmm. It certainly would have added a whole new source of puns to the language. Just think we could have called people who overuse casts ``narrowminded.''In addition, the proper syntax for a narrow operation would obviously be a skinny operator like ! or |. Seriously, though: JP: dynamic_cast(ptr/ref) only works if there is a _unique_ public base T of the full object. BS: Not quite. JP: narrow(ptr/ref) would select the public base T of the full object containing the base pointed to by the ptr/ref. The checked narrow is the complement to the always legal widening cast. Consider these cases: struct A {}; a-> A A struct B : A {}; | | struct C : B {}; B B struct D : B {}; | | struct E : C, D {}; C D \ / C* c = new E; // widen E A* a = c; // widen A dynamic_cast(a) is ambiguous. Even though the user was only concerned about narrowing A's to B's, later derivations have made this operation useless. BS: No: B* p = dynamic_cast(a) yields a pointer to the B containing the A pointed to by `a' exactly as you seem to want. Logically, dynamic_cast searches down through the class hierarchy until it finds the class it is looking for or the most derived class. If it gets to the most derived class without having found what it was looking for it looks ``back up'' with the full knowledge of the structure of the derivation lattice, and ambiguities may result. JP: However, narrow(a) is not ambiguous. It would select the B object containing the pointed to A object. struct A {}; A struct B : virtual A {}; / \ struct C : virtual A {}; b-> B C struct D : B, C {}; \ / D B* b = new D; A dynamic_cast(b) succeeds. The user has found a sibling object, probably unexpectedly, considering all the other possible subclasses of B. BS: I disagree. Casting to siblings is an important operation needed by many. The basic scenario is like this: I have an object of some type, probably abstract class A. I want to figure out if the object also support some other set of operations B. I do not know exactly how a user built up the class providing B, maybe by deriving from A: class B : public A { ... } and maybe by deriving from both A and B: class C : public A, public B { ... } or maybe in some even more indirect way. the point is that the object I'm looking at ia an A and it can do B operations. The most common case I have seen is actually A and B both being derived from a common virtual class V: V / \ A B \ / D I start out with a pointer to V, finds A using dynamic_cast, and then want to get to B. I could do that by first going to V, but the code that has the A may not know that its A originally came from a V and may not want to know about Vs.. JP: A narrow(b) properly fails. If the user wants the sibling C of D, the user can narrow to D then widen to C. There are no surprises. BS: No, because there is no reason to require that a user should know the common derived class D. There can be many different classes D1, D2, D3, ... all of which has an A and a B. Using cross-hierarchy casting, a user doesn't have to know about any D. The Ds are not relevant to the users needs, forcing the user to know about tham would spoil one of the beauties of using abstract classes. Maybe in some cases you don't want cross-hierarchy casts, but certainly others want them for some of their cases. I doubt that protection against cross-hierarchy casts is sufficient reason to introduce an extra operator. JP: struct A {}; a-> A struct B : virtual A {}; / \ struct C : B {}; B B struct D : B {}; | | struct E : C, D {}; C D \ / A* a = new E; E A dynamic_cast(a) is ambiguous. A narrow(a) is ambiguous. The only time a narrow can be ambigous is when narrowing from a shared virtual base. BS: Here we agree. JP: In my experience, a checked narrow is far more usable in code that needs RTTI than just a dynamic_cast. Why? The first time I hand-rolled a narrow macro boilerplate, I did it wrong, and ended up with the functionality of dynamic_cast! Jumping between siblings unexpectedly is just not fun. ;) BS: and neither is not being able to do it. JP: Anyway, can anyone satisfy my curiosity... Has it been considered and dismissed? BS: Yes, and many other variants as well. JP: And if so, why? BS: Because some of us really needed casting to siblings. In fact, to some people casting to siblings is what makes RTTI worth while.