The (B)Leading Edge
Faking (and exploring) Run Time Type Information

by

Jack W. Reeves
©C++ Report (September 1997)

 

Let’s face it, trying to maintain C++ code in a multi-platform environment is a problem. Once upon a time, there was a defacto standard for C++ -- either you were cfront compatible or you weren’t really doing C++. Then along came the standardization effort. After it got rolling, you had two defacto standards, cfront (which went through a couple of revisions itself), and whatever was in the latest draft working paper of the standardization committee. Never mind that the latter was a "draft" and a "working paper" not available to the public at large; all of the major compiler vendors are members of the committee. This latter "standard" rapidly began to contain features that were not available in cfront. This would not have mattered if the draft working paper was just paper. As the committee added features to the language, however, they needed feedback about those features. They needed feedback both from language implementers, and from actual users. In order to get such feedback, somebody had to actually add those features to their C++ compilers.

So, over time various vendors added various features from the draft working paper to their various offerings. As near as I can tell, no two vendors added the same set of features to their products. Also, once added, vendors were under no obligation to update their implementation of the feature to keep up with the latest revisions of the draft standard. The result has been a proliferation of different versions of C++.

There are really three different problems with trying to maintain a multi-platform C++ code base. The first problem is the typical one of trying to find the least common denominator of language features that is supported on all platforms. In this case, cfront is still a pretty good target. Unfortunately, the second problem is that the subset identified in part (a) is likely to change over time. cfront’s C++ is not a strict subset of (draft) Standard C++ and as different versions of compilers come out, support for some features actually diminishes. Likewise, certain implementations of certain features are not strictly compliant with the latest CD and so new versions of these implementations can be expected to change how things work. Someday, we can expect implementations of C++ that no longer support the old features. This means that in addition to finding the common subset that is currently supported, you want to try to avoid those features that you expect to disappear or change in a future release.

The final problem applies to even single platform development: programmer moral. Imagine you are a competent C++ programmer. You understand classes, and virtual functions and even most of the things in Scott Meyers’ books. How would you feel if you were assigned to do maintenance of a bunch of K&R C code (those people didn’t even use function prototypes for pity’s sake). Now move forward 5 years and put yourself in the shoes of a Standard C++ programmer of that time; someone with 2-3 years of experience under their belt; someone who knows and understands things like templates, exceptions, RTTI, and the in’s and out’s of the Standard library. How do you think such a person would feel about maintaining the code you are writing today (you’re not even using the standard ‘string’ class for pity’s sake)?

As a result, one of the informal responsibilities I have is to "push the envelope" of what language and library features are used. Keeping our code moving ever closer to (draft) Standard C++ is a challenge, but it has to result in easier maintenance in the long run. One of the features in (draft) Standard C++ that is not available on all our platforms is the RTTI facility. I find this especially annoying since all of our platforms now support exception handling, and some type of RTTI mechanism is required for exceptions. In any case, in the absence of the standard facility, our code base had developed a couple of different RTTI schemes. This is not unusual -- some form of RTTI support is usually required for any non-trivial class hierarchy (one of the reasons given for making it a part of the language). At some point, I decided that I could combine all the various home grown RTTI schemes into one, and in the process provide a reasonable approximation of the standard version. Doing this would allow us to start to write code that would be much closer to what we would write using the standard RTTI mechanism. This would give us some experience with the RTTI mechanism, and it would also make it much easier to convert our code to the standard at some point in the future. The process of implementing my own home grown RTTI facility has taught me quite a bit about how a real RTTI mechanism must work.

RTTI overview

The standard RTTI mechanism consists of three parts:

operator typeid

operator dynamic_cast

class type_info

typeid is a new keyword in the language. As a built-in operator it is much like the sizeof operator. It can be applied to any object or any type name in a program. It returns a reference to a const type_info object that contains the type information. The key difference between typeid and sizeof is that under certain circumstances typeid will provide information about the object’s runtime type, not just the static compile-time information. This happens when you apply typeid to a polymorphic reference –- that is a reference to an object of any type which has one or more virtual functions. In such a case, typeid will return the type information about the actual object being referenced. If typeid is applied to an object or a reference of a type which has no virtual functions, then the static (i.e. compile-time) type information is returned. Likewise if typeid is applied directly to a type name. If you accidentally apply typeid to a dereferenced null pointer, a bad_typeid exception will be thrown.

The class type_info is part of the standard library. The relevant declaration from CD2[1] is shown in listing 1. As you can see, the only information about the type that type_info apparently provides is the type name. I say "apparently provides" for a couple of reasons. As we shall see below, there are several other pieces of information which must be available at runtime in order to support the dynamic_cast mechanism. While the (draft) Standard offers no hint about how an implementation might provide that information, the obvious place to put it is in the type_info class since type_info has to be there anyway. In addition, there is a lot of other information about a type that the compiler has to have in order to do its job. A lot of that information could be made available in an extended type_info object if an implementation wished to do so. Since the typeid operator specification calls for it to return a reference, an implementation is free to actually return a class derived from type_info. I take advantage of this in my own implementation. If you are using the RTTI mechanism built into your compiler, see your vendor’s documentation to determine exactly what is available via the typeid/type_info mechanism.

The last, and in many ways the most important, component in the RTTI collection is the dynamic_cast operator. The standard party line is that dynamic_cast does a runtime check to make sure the cast is safe before actually performing the cast. As a result, most people translate a dynamic_cast<Target>(x) in their minds to

if (typeid(x) = typeid(Target))

static_cast<Target>(x);

While this is probably what most people use a dynamic_cast for, it falls short in describing the full capabilities of this operator. Consider the hierarchy in figure 1. If we have

A* a = new C;

B* b = dynamic_cast<B*>(a);

we expect the cast to succeed. In this case, the implementation is more like

if (*a is_a Target)

static_cast<Target>(a);

The non-existent is_a operator would determine if the object referenced is of the type Target, or one publicly derived from Target.

In addition to being able to do safe down-casts, dynamic_cast can also be used to safely (and correctly) perform three other types of casts that are not supported by any other facility within the language. Consider the hierarchy in figure 2. Suppose we have

A* a = new C;

B* b = dynamic_cast<B*>(a);

This is known as a cross-cast. Unlike a down-cast, a cross-cast can not be done without runtime type information. The problem is that the B-subobject is located at a different address within the C-object than the A-subobject. In order to correctly perform the cast the value of the pointer must be adjusted. If this is not done, then all we have done is change the type of the pointer (a reinterpret_cast). In order to correctly adjust the pointer, it is necessary to know how the A and B objects are related within the complete C-object. Since the cast may be attempted within a translation unit that doesn’t even know type C exists, this information has to be provided at runtime.

In a similar vein, consider figure 2 again and the following:

B* b = new C;

void* p = dynamic_cast<void*>(b);

This is a void-cast. It may seem a little strange to use a dynamic_cast for this since the language allows any pointer type to be converted to a void* -- without even an explicit cast. The problem in this case is that the pointer to the B sub-object probably does not point to the beginning of the complete C object. If b is implicitly converted to a void* the compiler will simply do a reinterpret_cast. If what is really needed is the memory address of the complete object, then a dynamic_cast must be used. One place you might need this is if you try implementing a custom memory management scheme. You can invoke C’s virtual destructor through a pointer to B, but you can not safely pass the B* to your deallocate() function.

The final use of a dynamic_cast is to down-cast from a pointer to a virtual base. Consider figure 3 – the dreaded multiple inheritance diamond. This type of situation requires that A be a virtual base class of both class B and class C. Now consider

A* a = new D;

B* b = dynamic_cast<B*>(a);

This is only possible using a dynamic_cast. If you try to use a static_cast, the compiler will give you an error. This is because when virtual inheritance is used, the final type only contains a single A object. Like the cross-cast, in order to correctly perform this cast, the pointer will probably have to be adjusted. Again, this means that the actual type of the object must be known before it can be determined where the B object is in relationship to the A object.

While all the above examples used pointers, the dynamic_cast operator can also be applied to references. The difference is that if a dynamic_cast of a pointer type fails, a null pointer is returned, whereas if the dynamic_cast of a reference fails, a bad_cast exception is thrown.

Faking it

The first step in building a home-grown RTTI mechanism was to create some macros and templates to represent the interface. Listing 2 shows this interface (I have included the macros which supply the other cast operators for reference – if you are going to provide dynamic_cast, you might as well have static_cast, const_cast, and reinterpret_cast also). The macros HAS_TYPE_INFO and HAS_RTTI are intended to be included in the public section of any class definition that expects to have RTTI operations applied to it. As you can see, both macros declare two member functions -- the static function type_descriptor(), and the non-static function type_id(). The primary difference between HAS_TYPE_INFO and HAS_RTTI is that type_id() is a virtual function in the latter. The static type_descriptor() function returns a reference to an extended_type_info object. This is the type_info derived class that supports the dynamic_cast operation. I created the HAS_TYPE_INFO macro against the possibility of needing RTTI information on a class that did not have virtual functions. I have not had any real use for it and I will not discuss it further. On those platforms that support standard RTTI, these macros are defined as empty.

The typeid operator is simulated by a template function of the same name. On platforms that support the actual built-in typeid operator, this template is not declared. The dynamic_cast operator is a macro like the other cast operators. It calls the docast() template function. This function takes the object (actually the expression) to be cast and the name of the target as a string (string-ized by the preprocessor operator ‘#’). The docast() function does the actual work of checking the validity of the cast. It returns a pointer, either the original expression pointer adjusted as necessary, or null to indicate a failure. The macro then casts this pointer to the desired type. Note that the docast() function expects a pointer as its first argument. As a result, the dynamic_cast macro can only be used to cast pointer types -- it can not be used to cast a reference. On platforms that support the built-in dynamic_cast operator, the macro simply invokes the built-in operator.

This interface covers about 80% of the typical usage of RTTI. Unfortunately, 80% isn’t quite enough. There were a couple of expressions which the real RTTI mechanism supports which I could not simulate with just this interface. One of these is the ability to apply the typeid operator directly to a type name (e.g. typeid(string);). This can come in useful in certain situations. My typeid template function has to have a real object as its parameter. At first I tried to get by with kludges like typeid(Typename()), but this was unsatisfactory. At best it causes the construction and destruction of a temporary object for what should be a simple constant time operation. At worst, it will not work for those types that do not have default constructors. Eventually, I created another macro, typename_typeid. This macro directly invokes the static type_descriptor() function (declared by HAS_RTTI) using the type name given as its argument. On platforms that have real RTTI, this macro just invokes the real typeid operator. Eventually, when all of my platforms support standard RTTI, I will have to clean up code that uses typename_typeid, but this will be a straight forward edit script, and in the meantime I have portable code.

The other problem is with dynamic_cast. As noted above, the macro can only cast pointer types. The real dynamic_cast operator can also be used to cast a reference. At first I was not too concerned about this. After all, if someone really wanted to deal with an exception, they could always test the returned pointer and throw the exception themselves. Then I discovered what a dynamic_cast of a reference is really good for.

A lot of times in dealing with inheritance hierarchies, especially those that have been extended, you have to down-cast an argument from a generic base class reference to a specific concrete type. You "know" what the actual concrete object type will be if the function has been called correctly, so a static_cast is the appropriate choice. Nevertheless, if you are like me and just a little bit paranoid you may decide to make sure the type is correct, at least during debugging. At first you might try something like:

assert(typeid(x) == typeid(Expected));

Expected& ex = static_cast<Expected&>(x);

This is fine, as far as it goes. Unfortunately, it doesn’t quite go far enough. If the type passed in is something derived from Expected then the assertion will fail when it should not. When you realize this, you might try

Expected* ep = dynamic_cast<Expected*>(&x);

assert(ep);

This is almost exactly what the dynamic_cast of a reference does; the only difference is you get a bad_cast exception thrown instead of whatever behavior the assert macro provides (a call to abort() by default, a logic_error exception on my implementation). Of course, the other difference is that you can not disable the dynamic_cast operator by defining the preprocessor symbol NDEBUG like you can the assert macro. What you really want is the non-existent is_a operator mentioned above. Then you could write

assert(x is_a Expected);

Expected& ex = static_cast<Expected&>(x);

I decided that I could create a version of is_a as a macro.

The down_cast macro is at the end of listing 2. As you can see, it has two forms: if the macro NDEBUG is not defined, the macro uses the docast() function to check the validity of the cast. The cast is then done as a static cast. If the check fails, a bad_cast exception is thrown. When NDEBUG is defined the test is removed; only the static cast remains. On platforms that support real RTTI, this macro becomes:

#ifndef NDEBUG

#define down_cast(TARGET, EXPR) dynamic_cast<TARGET>(EXPR)

#else

#define down_cast(TARGET, EXPR) static_cast<TARGET>(EXPR)

#endif

Since I created it, I have found a lot of use for this macro. I even prefer to use it in cases where I know that a static_cast is perfectly safe just because I like the self documenting form of the idiom and I know it doesn’t add any overhead to the released software. When all my platforms support Standard C++, I will probably change it to a template

template<class Target, class Expr>

inline Target down_cast(Expr x) {

#ifndef NDEBUG

return dynamic_cast<Target>(x);

#else

return static_cast<Target>(x);

#endif

}

This form is invoked just like a regular cast expression:

T& y = down_cast<T&>(x);

No matter what form it takes, you can be sure that some form of down_cast will remain in my code.

Now, the implementation. Naturally, everything is related to every other thing making it hard to describe in a linear fashion, but I will try. The first thing I needed was a real type_info class. Listing 3 shows the definitions of class type_info, and class extended_type_info. Class type_info is taken straight from the definition in the (draft) Standard. I added the private data member ‘name’ to hold the pointer to the name string, and the protected constructor. All the member functions (except the virtual destructor) are defined inline.

The class extended_type_info derives from type_info. As we will see in a moment, objects of this type are what are actually returned by the typeid operator. Originally, I added the member ‘size’ and the access function just as an example of the kind of information that an implementation might add to an extended_type_info class. Naturally, the size information is the same as would be returned by the sizeof operator applied to the class being examined. The difference is that when size is part of type_info, you can get the dynamic size of the object, not just the size of the base class. After I created this RTTI scheme, I discovered a place where I could use the size of the actual object (in a custom memory manager). As a result, I am now kind of annoyed that the C++ committee did not at least make size a part of the standard type_info class. Without it, my memory manager has to use some other scheme for keeping track of the actual object size since the extended_type_info method is not portable.*

The other information in the extended_type_info class is the number of public base classes of the subject class, a void*, and a pointer to an array which contains pointers to the extended_type_info objects of each base class of this class. We will discuss these items when we look at how docast() is implemented.

Corresponding to the HAS_RTTI/HAS_TYPE_INFO macros which are used within a class definition, there are MAKE_RTTI/MAKE_TYPEINFO macros which actually define the functions declared in the HAS_RTTI/HAS_TYPE_INFO macros. In the case of MAKE_RTTI there are actually an number of different macros. These are shown in listing 4.

The extended_type_info object for a class is declared as a local static object within the type_descriptor() function. The constructor for the object takes the name (the first parameter to the MAKE_RTTI macro), the object size, the number of base classes, and a pointer to an optional base class information array. The trailing digit in the name of the MAKE_RTTI macro indicates the number of public base classes (I know this is probably not the most intuitive naming convention, but it works). The macros MAKE_RTTI_1, MAKE_RTTI_2, etc., each declares a static array within the type_descriptor function which is initialized with pointers to the extended_type_info objects of the base classes. A pointer to this array is then kept as part of the classes’ type information. In this way, once you have a reference to the type_info object of a class, you can access the information about all its direct and indirect public base classes.

Basically, the type_id function just calls the type_descriptor function to get the extended_type_info object reference and returns it. Since type_id is a virtual function (typically), this guarantees that the actual most derived classes’ type_descriptor function is called. The type_id function performs one other critical task. After obtaining the reference to the extended_type_info object, but before returning it to the client, type_id stores within the extened_type_info object the current this pointer. Furthermore, it also invokes the type_id function of each base class. This results in a series of calls to the type_id functions of each direct and indirect base class of the current object. Each of these calls puts the current object pointer into the extended_type_info object. When the type_info object is finally returned to the client, it contains within itself a pointer to the current object, and indirectly a pointer to each base class object within the current object.

This is necessary to support the cross-cast capability. What I really wanted was for the type_info object to contain the static offset locations of the various base class objects, but I could not figure out any easy way to get at that information. Therefore, I force the compiler to provide it for me. For each of the calls of the form BASE::type_id() the compiler must first adjust the this pointer so that it is pointing to the BASE subobject within the complete object before the function is entered. The function then captures that value and stores it within the type_info object. Thus, after a virtual call to type_id such as is made during a dynamic_cast, the resultant type_info object will contain the address of the complete object, and via its ‘baseInfo’ member, the location of every base class object within the complete object. Obviously, this scheme is not going to work safely in a multithreaded environment, but it works fine within a thread.

In my environment, we seldom use multiple inheritance, so I only created MAKE_RTTI macros for up to 2 base classes. This can be easily extended for more extensive multiple inheritance hierarchies.

The heart of the implementation is in the two template functions (typeid and docast), and the support function _type_is_a used by docast. Listing 5 contains the definitions of the template functions; listing 6 contains the definition of miscellaneous other functions -- primarily the utility function _type_is_a.

The definition of typeid() is straight forward -– it uses its argument expression to call the type_id() function (usually virtual), which in turn calls the class’ type_descriptor() function as discussed above. The expression is tested before being used to make sure it is not a dereferenced null pointer; a bad_typeid exception is thrown if necessary.

The definition of docast() is only slightly more complicated -– the comments basically say it all. The string which represents the target type is parsed to find just the type name. This means striping off the trailing ‘*’ or ‘&’ (we assume we are casting a pointer or a reference), and any const/volatile qualifiers that might precede the type name. The resulting string is then passed to the _type_is_a function, along with the type_info of the expression which we are trying to cast.

The _type_is_a function is the heart of the dynamic cast. It seems deceptively simple (recursion often does). After doing a static_cast to recover the extended_type_info object from the type_info reference passed to it, _type_is_a checks to see if the type name of the object is the target we are seeking. If it is, or if the target is "void", then we have succeeded and the object pointer within the extended_type_info object is returned. If these first tests fail, then _type_is_a recursively invokes itself for each base class referenced in the extended_type_info object. If any one of these tests succeed, then the resulting pointer is passed back as the final result. If all tests fail, a null pointer is returned.

Exploring it

Obviously, a real RTTI implementation will not look like this. For one thing, the compiler will not be forced to manipulate type names as strings. Nevertheless, I feel like my home grown RTTI mechanism has given me some good insights into how the any real RTTI mechanism must work. Most of these insights are not particularly revealing:

The most important insight concerns dynamic_cast -- in general, a dynamic_cast is not a constant time operation. I think this is particularly important because I suspect that most people think of dynamic_cast as a constant time operation. While the actual runtime overhead of any dynamic_cast depends upon a number of factors, two special cases appear likely to be important. The first is the case where the target type of the cast is indeed the actual type of the object. This is likely to be the case in the vast majority of circumstances where the down_cast macro is used (or any dynamic_cast of a reference). In this special case, a dynamic_cast actually is a constant time operation. The other special case is when the dynamic_cast fails. This is the worst case scenario. Before it can report failure, a dynamic_cast must check all direct and indirect base classes. This means that the runtime overhead of a dynamic_cast that fails is O(n) where n is the number of base classes of the actual object. In most inheritance hierarchies, n is not likely to be very large, but it is something to keep in mind.

The most revealing bit of information about dynamic_cast performance really has nothing whatsoever to do with casts – instead it has to do with exceptions. I noted above that support for exceptions implies some type of RTTI mechanism. This is true because an exception in C++ can be an object of any type. Since the code at the catch site does not have any idea what type of object was thrown, some sort of type information must accompany the exception object itself in order for the exception runtime mechanism to determine which handlers can catch the exception. Locating a handler to catch an exception basically becomes a test similar to:

if (exception is_a Handler_exception_type)

This is our non-existent is_a function again. Put another way, the search for an appropriate handler for any given exception conceptually involves doing a dynamic_cast to see if the actual exception can be cast into the type required by the handler. This test is attempted for every handler in turn. Since we can typically expect that most handlers will not be able to handle a given exception, then we can expect that the search for a suitable handler will involve a lot of dynamic_casts which fail. As noted above, this is the worst case situation. This drove home for me, like nothing else before, just how expensive it is to throw an exception.

Wrapping up

Once RTTI was available and people started to use it, a few other observations about RTTI became apparent. The first is that it really is useful and having the standard version available (or a reasonable facsimile) means that developers don’t waste time re-inventing the wheel when something similar is needed.

The second observation is that RTTI is seductive and having it available means that people can get lazy. For myself, I developed a tendency to write things such as:

if (typeid(x) == typename_typeid(Y))

Y& y = static_cast(Y&, x);

instead of the preferred

if (Y* y = dynamic_cast(Y*, &x))

Since not all of my compilers support the latter, I adopted the following idiom:

if (dynamic_cast(Y*, &x)) {

Y& y = static_cast(Y&, x);

. . .

(I am sure this will drive some future maintenance programmer nuts). In a few other occasions, people have caught themselves writing cascading RTTI tests and realized that doing so meant that the design needed some enhancement, like an additional virtual function.

The third discovery was that Standard RTTI is not a total panacea; for one case we eventually had to create a library specific version of RTTI. This is probably a rare situation, but it doesn’t hurt to mention it. Just as RTTI is no substitute for good design and the appropriate use of virtual functions, there can still be cases where some form of RTTI is the right choice, but Standard RTTI is not.

In general, however, having Standard RTTI available is a Good Thing -– even if you have to build it yourself.

 

Listing 1.

Class type_info

class type_info {

public:

virtual ~type_info();

bool operator==(const type_info& rhs) const;

bool operator!=(const type_info& rhs) const;

bool before(const type_info& rhs) const;

const char* name() const;

private:

type_info(const type_info& rhs);

type_info& operator=(const type_info& rhs);

};

 

Figure 1.

Figure 2.

Listing 2 - Interfaces

// new casting operators

#define const_cast(TARGET,EXPR) ((TARGET)(EXPR))

#define reinterpret_cast(TARGET,EXPR) ((TARGET)(EXPR))

#define static_cast(TARGET,EXPR) ((TARGET)(EXPR))

// RTTI stuff

// forward declarations

class type_info;

class extended_type_info;

#define HAS_TYPE_INFO \

static const extended_type_info& type_descriptor(); \

const type_info& type_id() const

#define HAS_RTTI \

static const extended_type_info& type_descriptor(); \

virtual const type_info& type_id() const

// typeid

template <class T>

const type_info& typeid(const T& expr);

#define typename_typeid(TYPE) \

(TYPE::type_descriptor())

// dynamic_cast

template<class T>

const void* docast(const T* expr, const char* target);

#define dynamic_cast(TARGET, EXPR) \

((TARGET)docast((EXPR), #TARGET))

#ifndef NDEBUG

#define down_cast(TARGET,EXPR) \

( ((docast(&(EXPR),#TARGET)) ? (void)0 : throw bad_cast()),\

((TARGET)(EXPR)))

#else // NDEBUG version

#define down_cast(TARGET,EXPR) ((TARGET)(EXPR))

#endif // NDEBUG

Listing 3 - type_info classes

class type_info {

const char* _name;

public:

virtual ~type_info();

bool operator==(const type_info& rhs) const

{ return this == &rhs; }

bool operator!=(const type_info& rhs) const

{ return this != &rhs; }

bool before(const type_info& rhs) const

{ return this < &rhs; }

const char* name() const

{ return _name; }

protected:

type_info(const char* name)

: _name(name) {}

private:

type_info(const type_info& rhs);

type_info& operator=(const type_info& rhs);

};

class extended_type_info : public type_info {

size_t _size;

size_t _numPublicBases;

const void* _self;

const extended_type_info** _baseInfo;

public:

virtual ~extended_type_info();

extended_type_info(const char* name,

size_t size,

size_t numBases,

const extended_type_info** baseInfo)

: type_info(name),

_size(size),

_numPublicBases(numBases),

_baseInfo(baseInfo) {}

// access

size_t size() const

{ return _size; }

size_t num_bases() const

{ return _numPublicBases; }

const void* self() const

{ return _self; }

const extended_type_info& base_info(int i) const

{ return *_baseInfo[i]; }

// modify

const extended_type_info& self(const void* p) const

{ ((extended_type_info*)this)->_self = p; return *this; }

};

 

Listing 4 - MAKE_RTTI macros

 

#define MAKE_TYPE_INFO(NAME) \

const extended_type_info& NAME::type_descriptor() \

{ \

static extended_type_info tinfo(#NAME, sizeof(NAME), 0, 0); \

return tinfo; \

} \

const type_info& NAME::type_id() const \

{ return type_descriptor().self(this); }

#define MAKE_RTTI_0(NAME) \

const extended_type_info& NAME::type_descriptor() \

{ \

static extended_type_info tinfo(#NAME, sizeof(NAME), 0, 0); \

return tinfo; \

} \

const type_info& NAME::type_id() const \

{ return type_descriptor().self(this); }

#define MAKE_RTTI_1(NAME, BASE) \

const extended_type_info& NAME::type_descriptor() \

{ \

static const extended_type_info* binfo[1] = \

{ &BASE::type_descriptor() }; \

static extended_type_info tinfo(#NAME, sizeof(NAME),1,binfo); \

return tinfo; \

} \

const type_info& NAME::type_id() const \

{ BASE::type_id(); \

return type_descriptor().self(this); }

#define MAKE_RTTI_2(NAME, BASE1, BASE2) \

const extended_type_info& NAME::type_descriptor() \

{ \

static const extended_type_info* binfo[2] = \

{ &BASE1::type_descriptor(), &BASE2::type_descriptor() }; \

static extended_type_info tinfo(#NAME, sizeof(NAME),2,binfo); \

return tinfo; \

} \

const type_info& NAME::type_id() const \

{ BASE1::type_id(); \

BASE2::type_id(); \

return type_descriptor().self(this); }

 

Listing 5 - template function definitions

#include <string>

#include <typeinfo>

template<class T>

const type_info& typeid(const T& expr)

{

if (!&expr) throw bad_typeid();

return expr.type_id();

}

const void*

_type_is_a(const type_info& tinfo, const string& name);

template<class T>

const void* docast(const T* expr, const char* target)

{

if (expr and target) {

string tmp = target;

// strip off a trailing '&' or '*'

tmp.resize( tmp.find_last_not_of("&* ")+1 );

// now find just the type

string::size_type pos = tmp.find_last_of(' ');

if (pos != string::npos) {

tmp = tmp.substr(pos+1,string::npos);

}

// now see if expr is a type tmp

const type_info& tinfo = expr->type_id();

return _type_is_a(tinfo, tmp);

}

return NULL;

}

Listing 6 - rtti.C

#include <string>

#include <typeinfo>

// virtual functions

type_info::~type_info() {}

extended_type_info::~extended_type_info() {}

// _type_is_a function

const void*

_type_is_a(const type_info& tinfo, const string& name)

{

const extended_type_info& etinfo =

static_cast(const extended_type_info&, tinfo);

if (etinfo.name() == name or name == "void") {

return etinfo.self();

}

for (size_t i = 0; i < etinfo.num_bases(); ++i) {

const void* p = _type_is_a(etinfo.base_info(i), name);

if (p) return p;

}

return NULL;

}

References

1. "Working Paper for Draft Proposed International Standard for Information Systems -- Programming Language C++", December 1996 (2nd Committee Draft).