🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.std.c++
Article #897

Re: type/member tags (was Re: asking an object for its type)

#897
From: Bill Triggs
Date: Mon, 04 Mar 1991 18:53
239 lines
8998 bytes
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)<<CHAR_BITS | (b))<<CHAR_BITS | (c))<<CHAR_BITS | (d))
#endif

#endif _Token_h
==================================

eg:

const Token HELP = token('H','E','L','P');
Token t;
...
switch(t)
{
case HELP:
  ...
}	

=================================

-- 
Bill Triggs
Oxford University Computing Laboratory
11 Keble Rd
Oxford OX1 3QD, U.K.
Technical Details
Message-ID: <1399@culhua.prg.ox.ac.uk>
Path: arc1-ca.pugleaf.net!utzoo!news-server.csri.toronto.edu!cs.utexas.edu!uunet!mcsun!ukc!ox-prg!bill
References: <DSOUZA.91Feb24173454@optima.cad.mcc.com> <27C95D3A.1715@tct.uucp> <4201@lupine.NCD.COM>

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!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: <DSOUZA.91Feb24173454@optima.cad.mcc.com> <27C95D3A.1715@tct.uucp> <4201@lupine.NCD.COM>
Sender: news@prg.ox.ac.uk
Lines: 239