Path: utzoo!news-server.csri.toronto.edu!cs.utexas.edu!uunet!mcsun!ukc!ox-prg!bill From: bill@robots.oxford.ac.uk (Bill Triggs) Newsgroups: comp.std.c++ Subject: Re: type/member tags (was Re: asking an object for its type) Message-ID: <1399@culhua.prg.ox.ac.uk> Date: 4 Mar 91 18:53:16 GMT References: <27C95D3A.1715@tct.uucp> <4201@lupine.NCD.COM> Sender: news@prg.ox.ac.uk Lines: 239 I have been pleasantly surprised by the basic similarity of so many of the type-tagging schemes proposed during this discussion: perhaps those of us who do recognise the need for such things *CAN* agree on a partial standard until proper language support is available. The principal applications of type information seem to be: (i) Testing whether a Base& or Base* reference actually references a Derived instance, ie whether a (Derived) cast is legal. Type info is *essential* for safe down-casting. The alternative of pushing all Derived functions (and implicitly members) into the Base -- either as full implementations or as empty or error-calling stubs -- is simply laughable, although it has been suggested seriously enough by several people during this discussion. (ii) Creation of an instance of an implicitly specified class. For example when reading a persistent object from disk, remotely creating an object across a network, parsing an object- specifying input text, or simply creating a local heap object whose exact type is better not known at compile-time. It seems that an appropriately global `class-designator' is indispensable for this, whether string or global address or global enumeration constant, and that associated with this must be appropriate `static Class* Class::' or `friend Class*' new_object() or read_object(), parse_object() functions. The association can be (a) via an explicit switch() on the class-designator (which must then be translatable to an enum or const int type) (b) via a more dynamic association mechanism such as a hash table (c) the class-designator can be a reference to a class-type record for the class, which contains the appropriate functions. We all know the antipolymorphic evils of building explicit type switches into our code and hashing seems unnecessarily complicated, so (c) seems the best bet: ie a `class ClassType' instance for each class, acting as a `static function vtable'. NB: conditional casts handle only (i) above and are NOT enough by themselves. A `standard' type mechanism ought to meet the following requirements (at least): (i) Type functions must obviously be lightweight virtual member functions for most purposes. Given existing virtual functions the per-instance overhead for type info should be nil. Non-virtual type functions are not very useful, but might be allowed for small classes without virtual functions. (ii) Derivation as well as exact type must be testable --- via `isa()', 'isakindof()' or `object >= type' functions --- and this must work with multiple inheritance (ie it must detect all potential cast-ability). C++-style type forests must be supported. It is helpful for the isa() function to return (void*)this if casting is possible, and 0 otherwise so that CLASS* foo = (CLASS*)isa(CLASS::Type) is possible. (iii) Switch()-ing on type ought to be possible. For this type must be convertible to a compile-time-const int. Type must at least be easily convertible to and from an external, global, time-invariant const for network/disk-based applications, with little risk of clashes between different types. (iv) Type ought to be convertible to a record containing appropriate static object-creation functions (new_object(), read_object(), parse_object()), and other useful information such as class name and schema descriptors. (v) Even though there is no formal language support, type-ing a class should be easy & transparent. A single comprehensible macro call per class would be ideal. (i),(ii) and (v) are straightforward. Unfortunately, (iii) and (iv) are difficult to reconcile because some C++ implementations are very fussy about case label const-ness: in particular g++ (1.39.0) will not accept (casts of) link-time constants such as const struct or string addresses as labels. Given that switches (which work only on exact type) are probably undesirable anyway, I would vote for type-as-ClassType-record rather than type-as-enum-int. The record could always contain an enum if desired. Of course I wouldn't have bothered to discuss all this if I didn't have a barrow to push, so here is my sacrificial offering for the putative standard. It is obviously similar to many others that have been mentioned, but it seems to fit the requirements a little bit better than most (IMHO). I declare it public domain, not subject to patent (at least by me), and free to all. There is no guarantee whatsoever, but IMHO it feels `right' and it has worked well for me for a few months now!. Comments and unencumbered PD alternatives are solicited for comparison. The class ClassType is application dependent, application defined, and irrelevant to the type-checking mechanism. It probably contains things like a class-name string or enumeration const class-token, void* (*)() pointers to `static Class* Class::' or `friend Class*' object creation and reading functions, and class schema information. I see no hope of general agreement on the real contents of this, but an agreed ClassType base class stub might be useful so that types in merged applications don't clash fatally (the type system would then allow safe casting to the true ClassType-type on a per-instance basis, if ClassType itself had a type!). ======================================= // V1.0 30/X/90 -*-C++-*-; Bill Triggs (bill@robots.ox.ac.uk) Oxford AGV project. // type function macros for dynamically-typed classes #ifndef _typefunctions_h #define _typefunctions_h 1 #pragma once #include "ClassType.h" // For (obj.type() == CLASSNAME::Type) comparisons ClassType should define: // inline int ClassType::operator==(const ClassType& t) const { return (this == &t); } // inline int ClassType::operator!=(const ClassType& t) const { return (this != &t); } #define rootclass(virtual) typefunctions( ,virtual) #define subclassof(parent) typefunctions(|| parent::isa(t), ) #define subclassof2(p1,p2) typefunctions(|| p1::isa(t) || p2::isa(t), ) #define subclassof3(p1,p2,p3) typefunctions(|| p1::isa(t) || p2::isa(t) || p3::isa(t), ) #define typefunctions(or_isa_parent,virtual) \ public: \ static const ClassType Type; \ virtual const ClassType& type() const { return Type; } \ virtual const void* isa(const ClassType& t) const \ { return ((&t == &Type) or_isa_parent) ? this : 0; }\ #endif _typefunctions_h ======================================= Use this as follows: //--- header files -- one macro per class class Root { rootclass(virtual); // root class virtual type fns public: .... }; class Tiny { rootclass(inline); // not-so-useful: non-virtual root class type fns public: .... }; class Derived: public Root { subclassof(Root); // derived class type functions public: .... }; class Derived2: public Root, Tiny { subclassof2(Root,Tiny); // multiply derived class type functions public: .... }; //--- implementation files -- a ClassType CLASS::Type per class static const ClassType CLASS::Type(.....); //--- application files -- if (foo.type() != Root::Type) {...} if (foo.isa(Derived::Type)) { ((Derived)foo).bar(); } cout << foo.type().classname; ================================= For those who like switch()-ing on type, or need a global enum-type constant for network or disk-based type designation, I also suggest the use of `meaningful 4-char tokens' as below. Not a foolproof scheme, but a lot safer than an enum with several people adding to it in different files. Easier to debug too, especially with a simple routine to print Tokens as strings. The quotes are a bit messy but there's not a lot of choice if you need a compile-time constant. ================================== // V0.0 24/IX/90 -*-C++-*-; Bill Triggs (bill@robots.ox.ac.uk) Oxford AGV project. // parser token (4 char mnemonic "enumeration type") #ifndef _Token_h #define _Token_h 1 #pragma once #define CHAR_BITS 8 #define LONG_BITS 32 #if (4 * CHAR_BITS) <= LONG_BITS typedef unsigned long Token; #define token(a,b,c,d) ((((a)<