From: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Subject: Re: run-time type checking (was: Re: Covariant Types in Derived Classes)
Organization: MAXTAL P/L C/- University Computing Centre, Sydney
Date: Thu, 23 Jul 1992 15:42:54 GMT

In article <1992Jul22.022218.1115@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul21.162659.25474@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU  
>(John MAX Skaller) writes:
>> 	In general C++ programming I do not agree that it is
>> necessary to recover arbitrary type information. That is because,
>> quite simply, you CANNOT use it.
>>
>"In general C programming I do not agree that it is necessary to use  
>inheritance of structure. That is because, quite simply, you CANNOT use it"

	Sorry, I'm not sure what you mean be inheritance of structure.

> I don't like the argument. We are not talking about C++ programming. We are  
>talking about the enhancement of the C++. If this argument could make any  
>sense, why should we need C++?

	Well, we need C++ because it can do lots of nice things already.

>
>> 	I think I should clarify here: I am arguing against
>> DOWNCASTING. 
>> 
>I am against *unsafe* downcasting too. What we are arguring about is not the  
>downcasting. 

	Ah: you misunderstand! I REALLY ARGUE AGAINST DOWNCASTING.

>It is the run time type checking: whether it is necessary.

	Given the need to do downcasting, to do it safely run-time
type information is required. But I am arguing it is not necessary
to downcast. So the question of needing run-time type info to
do it doesn't arise.

>
>> 	I am not against collections of objects all derived
>> from a common base provided you access them via the base.
>> That is the vey point of inheritance, after all.
>> In this case the objects are all of the same type:
>> it is homogeneous at the level it is accessed and
>> heterogeneous at the actual object level, but the actual
>> object level is not accessible.
>>
>True. I'm always in favor of doing that. We can always add member funtions and  
>virtual member functions to aviod downcasting an element in a *domestic*  
>heterogeneous collection or structure. 

	Ah. I like your word *domestic*. But your statement shows backwards
thoughts: we do not add virtual functions to "avoid downcasting".
We design our systems properly in the first place, and then the need
to downcast never arises, when our thoughts wander in that 
direction one can be sure we are not thinking virtuously,
and perhaps the design itself is flawed.

	Inother words, in a *domestic* system, any apparent 
need for downcasting should be used as an indicator of flawed design.

>But this does not mean that the run time  
>type info is not necessary. In case of a foreign heterogeneous collection, we  
>often need the run time type info, need to store and export the type info  
>together with the object. 

	Ah, now *foreign* systems. Here, in general, we cannot
store objects directly, they must be encoded somehow. That encoding
is the run-time type info.

	The problem is that you don't HAVE th objects to get the run
time info FROM. You have the encodings, and the object type info
is in the encoding --- not the object. So of course you must
extract it to create an object of the appropriate type--- and to
do that implies you KNOW the run-time type BEFORE you create
the object, so it follows you dont need to ask the object
for its type---you already knew the type when you created it.

	The exception---the time you DO need run-time type info
is when you have a *meta-domestic* system. Like a debugger,
it is a system designed to manage systems, and BOTH happen
to be already C++. THEN you could use RTTI. 
>
>> 	I am not against collections of objects of completely
>> unrelated types provided the types are drawn from a finite
>> and statically known set. In this case there is a 'type' which
>> is the finite union of those types, so again, the collection
>> is really homogeneous wrt that type.
>>
>This case exactly examplifies the necessity of runtime type info. Given an  
>element in this collection, you need run time type info to identify its type.  
>Otherwise, how do you do with the element which may be in one of the types that  
>are *completely unrelated*, say { int, float, doulbe }?

	You need to know the type. But you DO NOT HAVE TO ASK THE OBJECT
WHAT TYPE IT IS. You have to store a code WITH the object that you
access separately. 

	And notice there is NO issue of downcasting here. The issue
is one of SELECTION. From a FINITE number of alternatives.
From a KNOWN FINITE number of alternatives.

	In the case of downcasting from a base to a derived class
you CANNOT know the alternatives, you CANNOT know what the
derived types are,m because THAT LACK OF KNOWLEDGE IS 
FUNDAMENTAL to the idea of inheritance. 
>
>> 
>> 	I am against using, in general programming,
>> DOWNCASTING or run-time type information to defeat the type system.
>>
>I just don't understand! 
>Run time type check is a facillity to prevent careless  
>downcast to defeat the type system. 
>How can you say that it is used in general  
>programming to defeat the type system?

	Because if you are using it in *domestic* programs to downcast
safely, you are using it to downcast, and downcasting is defeating
a promise made by the concept of inheritance: that promise is
called the OPEN/CLOSED principle. It is the cornerstone of
object oriented design (well, actually, class-oriented design).

	It makes the promise that an algorithm applied to
a class B will continue to work for ALL classes D1, D2, D3
where D1, D2, etc are derived fomr B.

	The way this promise is enforced is to hide ALL
details of D1, D2, D3 etc other than those shared in
class B from the algorithm.

	If you downcast from the base class B you are learning
something about, say, class D1 that you weren't supposed to know.
If you use that knowledge, you will become unstuck.

	Let me give you an example. Suppose class D1 has a function
f() while B has no such function. You have an algorithm that
downcasts to D1 from B and calls f().

	But then you change D1 to NOT have a function f().

	Suddenly, the algorithm FAILS. (It won't even compile).
[Too bad if the algorithm was locked away in binary form,
then it will fail at link time, or if linkage is dynamic, who knows?]

	Now you see you have caused an algorithm which, by its
declaration ONLY DEPENDED ON THE INTERFACE (and semantics :-)
of class B to fail --- BY MODIFYING D1, which is derived from
B. You have VOIDED THE OPEN/CLOSED PRINCIPLE by using a downcast.

Now again: before we can discuss when and how to use RTTI, I need
you to understand that the OPEN/CLOSED principle is voided
by using downcasting and to agree that this is not desirable.

I need you to see the issue IS NOT ABOUT THE SAFETY OF DOWNCASTING.

The issue is, how can one carefully discriminate the legitimate
use of RTTI in *meta-domestic* systems, and maybe in
*foreign* systems, from its illegitimate use for
downcasting in *domestic* systems.

> 
>> 	I agree that in Debuggers and other such programs
>> these facilities are essential, indeed, you need much more
>> than a mere safe downcast or run-time type code. In a debugger,
>> for example, you need in effect run-time access
>> to the complete declaration and definintion of each class in
>> the whole program!
>>
>A simple type id can be a key for a debugger to access to the complete  
>declaration and definintion of each class in the whole program. We are not  
>necessary to include the complete type information at run time.

	You do if the debugger is any good.
>
>> 	C++ does NOT have facilities at present to do this.
>> I am NOT against adding them. But I would be against using these
>> facilities in the general programming context for which C++
>> was intended and where such mechanism are *dangerous*.
>>
>Again, I can't understand. 
>Run time type check is not a dangerous mechanism. 

	Run time type information is INHERENTLY DANGEROUS, 
because it can provide access to information that the type
system has DELIBERATELY HIDDEN. The only reason you want run-time
information is that YOU DONT HAVE INFORMATION THE SYSTEM HAS
HIDDEN FROM YOU DELIBERATELY, PREICSELY BECAUSE ANY ATTEMPT
TO USE THAT INFORMATION IS UNSAFE TO USE.

	The fact that you can use it to convert a base class
does not make that conversion safe. Just because you get back
a pointer you  know is the right type DOESN'T mean
you were entitled to use that pointer.

>On  
>the contrary, it is used prevent potential dangers.
>  
>> 	Clearly, for example, a debugger which allows you
>> to modify instances of a class breaks encapsulation!
>> It breaks almost every principle you can think of. 
>> It is meant to: you use the debugger when a more important
>> thing is already broken : your program :-)
>> 
>Here, you goes too far. A debugger can break every principle of encapsulation  
>and protection only through low level programming facillities (e.g. the bitwise  
>and bytewise read/write, enforced typecasting between different types, machine  
>addresses, etc.). 

	You confuse 'breaking the system' with causing a fault.
To cause a fault, you need only write somewhere in memory
that you weren't meant to. To do that you MUST use a cast.
So casts are bad. (Conversions are not, however).

	A 'hacker' breaks system security when e accesses the system.
That doesn't mean the system is 'messed up', or the file system
destroyed. None-the-less, the system is *broken* when security
is breached *even if no damage is done*.

	In the case of downcasting the problem is the same as for
any old casting: you have access to information you shouldn't have.
The reason you shouldn't have it is that if you do you MIGHT
proceed to destroy the system integrity, by breaching
promises the system has made.

	For example, if I give you an animal pointer,
and it has read only functions ONLY, I expect the object to
remain unchanged.

	If you cast it to a Mammal, which has mutator functions,
and proceed to mutate it, I will be very annoyed. I want the
system to PROMISE ME YOU CANT DO THAT, at least not privately.

>But a debugger can never break any principle of encapsulation  
>and protection only through run time type check. 

	OF COURSE IT CAN. The debugger has access to the
private data of an object. The debugger is not a member function
of the object. THEREFORE, unequivocably, the debugger has broken
the encapsulation of the object (even if it is a benign debugger
and wont abuse the priviledge).

>Remember: run time type  checking does not allow to cast objects 
>which are actually of different types.

	Agreed. But it facilitates casting. Casting is bad.
(Usually) Since we dont want to cast things, we dont need to
do it safely!

>
>Even we stick to homogeneous programming, we still need run time type checking.  
>One of your reason against the concept of "thisclass" is that it needs run time  
>type checking, which you view as a disadvantadge. "thisclass" is just a  
>facillity to describle homogeneous data structure.

	Yes. I'm not necessarily against 'thisclass', I merely pointed out
that it has some disadvantages. Requiring RTTI is a disadvantage
because we dont yet have it :-)
>
>Okay, let's get rid of the concept of "thisclass" for the time being. Now, is  
>it true that run time type checking is no longer needed for homogeneous data  
>structure? No. 

	We still disagree: for domestic systems, yes, RTTI is NEVER 
needed. I've never needed it. I always know what types I'm working with!

[Actually, i cheat a bit in template classes to achieve code reuse,
I sometimes cast void* to T*, but is not NECESSARY, just expedient]


>Without covariant type specification (now, we come to our  
>original subject) in derived classes, we still have to run time type check the  
>type of arguments in derived classes in order to prevent any violation. 

	NO, you have to accept the restriction of single dispatch.
	The ONLY object you know the type of is the *this object.
	(And any others passed by value).

>Let's  
>consider a homogeneous animal collection:
>
>    class Animal;
>    class AnimalGroup
>    { protected:
>       Animal * animals;
>      public:
>       virtual void accept (Animal *ap) {...};
>        ...
>    };
>
>Since it is a homogeneous collection, it should contains animals of the same  
>type. The concept of "same type" is relatively dependend on what the class  
>designer means. 
	
	No, quite wrong. Same type means 'common properties
of all animals'. It SPECIFICALLY excludes you knowing about
whether the animal has webbed feet. Because snakes dont have
feet, so its not a property common to all animals.

>Suppose the same type" is based on the *family* according to  
>the systemtic zoology. Then the collection should be of all dogs, or all cats,  
>etc. 

Then you should have a collection of all dogs, or all cats,
not all animals.
This is why some people like templates a lot.


Now you have further example of a serious problem, but I will
simplify it.

The problem is why you proposed 'thisclass' and is a real problem.
It is a problem for which RTTI is the wrong solution,
but never-the-less is the best we will get for a while.
We really need multiple dispatch.


class Animal {
	int mate(Animal&);
};
class Dog : Animal {
	int mate (Animal&);
};

We REALLY want Dog::mate(Dog&), but we cant have it.

This is a flaw in Object Oriented Programming IMHO.
The solution requires multimethods which are inherently
functional.


-- 
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
	Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------