TITLE: Designing a down-cast facility PROBLEM: The problem is to come up with a "down casting" facility for use in Guide clients. The mechanism needs to look something like the following: TBase* b = ...; TDerived* d = DOWNCAST (TDerived, b); The derived pointer should be safely cast from the base pointer. If the base pointer is not of the correct class, an assertion should result. ATTEMPT #1: #define DOWNCAST(T,p) \ (T*)p; \ ASSERTION(p->isA(MAKE_STRING2(T)),"good downcast") This looks good at first glance, except that clients typically will make calls like the following: extern TBase* foo (); TDerived* d = DOWNCAST (TDerived, foo()); Critique- The macro evaluates the parameter "p" twice, resulting in multiple calls to foo() (highly undesirable). In addition, the ASSERTION macro expands to multiple statements, causing potential trouble when used with unblocked "if" statements. ATTEMPT #2: Let's try using templates. In this case, we are supplying the source and destination class types to the template. We run up against a template restriction in this case. For the compiler to "instantiate" the template, all the parameters to the template must appear as arguments to the function "cast". The "D" argument only shows up as the return type for the function. Hence, we use the trick of putting the function in a dummy struct to get around this limitation. template struct CastDummy { static D* cast(S* p) { FUNCTION (CastDummy::cast); ASSERTION(p->isA(D::className())); return (D*)p; } }; Critique- While this will work, it introduces the use of templates. In addition, the "cast" method is likely to complex to expand inline. ATTEMPT #3: The ANSI committee has recently approved an extension to the template limitation above by allowing the following: template D* cast_down(S* p); // similar to above, but no dummy struct D* d = cast_down(p); In this example, the compiler will automatically bind the type in the <> brackets to the first template argument. Critique- Not available yet. Still uses templates. ATTEMPT #4: Try a different workaround to the template limitation - use a dummy argument: template D* down_cast(TObject* p, const char* d_name, D* dummy) { ASSERTION(p->isA(d_name),"good downcast"); return (D*)p; } #define DOWNCAST(T,p) \ down_cast(p,MAKE_STRING2(T),(T*)0) Critique- Still uses templates. But by using a macro, we can use the stringization to avoid assuming isA() for the classes. ATTEMPT #5: Lets skip the templates and go for static functions in each class. This function can be slipped into the STANDARD_METHODS and STANDARD_CLASS_HEADER macros already in our code. class TSESDesignNode { ... static TSESDesignNode* downCast(Tobject* p) { FUNCTION (downCast); ASSERT(p->isA(className())); return (TSESDesignNode*)p; } ... }; TSESDesignNode* sd = TSESDesignNode::downCast(n); Critique- The downCast() method is too complex to inline. We will have code generated for each class, whether downCast() is needed or not. ATTEMPT #6: Make a single function (that assumes TObject) and combine it with a macro: TObject* verify_isa (TObject* p, const char* d_name) { FUNCTION (verify_isA); ASSERTION(p->isA(d_name),"good downcast"); return p; } TOther* verify_isa (TOther* p, const char* d_name) { ASSERTION(p->isA(d_name),"good downcast"); return p; } #define DOWNCAST(T,p) \ (T*)verify_isA(p,MAKE_STRING2(T)) Critique- Now only have minimal amount of code. Designers can use function overloading to handle other non-TObjects. All clients can still use the same macro for all cases.