🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

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

Re: run-time type checking (was: Re: Covariant Types in Derived Classes)

#53
From: John MAX Skaller
Date: Sun, 02 Aug 1992 12:15
258 lines
9316 bytes
In article <1992Jul31.230135.1201@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul30.172830.18501@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU  
>(John MAX Skaller) writes:
>> 	I refered to compiler generated RTTI. User generated RTTI
>> must obey the current restrictions of C++, and so is as sound and safe
>> as C++ currently is. I also refered to a specific case in one place,
>> and say in general in another, intending to say 'always'. OK?
>> 
>I cannot understand why user generated RTTI is sound and safe while the  
>compiler generated RTTI is not.

	This not what I meant. Compiler generated RTTI just *is*, it
is neither sound, nor safe nor anything else. The issue is how you
ACCESS it.

	The form of RTTI in the animal example (virtual Species()
function) is not necessarily safe, as you have pointed out (one
could assign the wrong species name). But is IS sound, that is,
it is type-safe and preserves the open closed principle because
it uses existing sound C++ mechanisms, namely, virtual functions.
It does NOT downcast--downcasting, like all casting, is unsound
(in general).

	The difference between downcasting to any old derived
class an using existing mechanisms, is that no existing
mechanism (other than casting) can provide you with access
to an interface other than a SUBSET of the one you know the
object by. Downcasting provides access to a SUPERSET of the
interface.

	Providing access to an object via a base class ONLY
is a restriction. That restriction is what enables polymorphism
to work properly. Downcasting removes that restriction and thereby
allows access to the exact type of the object, and to interfaces
that you were not originally granted access to. 

	If there is something *we* dont not understand, it is this:
what guarrantees are voided (lost) if one gains access to an object
via an interface by which it was not statically known?


	class X {
	protected:
		int x;
	public:
		int get()const {return x;}
	};

	f(X&);

	class Y : public X {
	public:
		void set(int y){x=y;}
	};

	Y aY;

	f(aY);

Please consider this example carefully, because I think it summarises
our points of view.

In my view f should not be allowed to modify its object.
To me, a proper language system would guarrantee that.
If it does, I can take advantage of the fact that i know
that aY has not changed.

In your view, it would be allowed to (by downcasting the X to a Y,
then using the 'set' method.)
In your view, the explict interface with which f is declared
need not constrain what behaviours the X object may exhibit.

>If you think "downcasting is OK when you already knew what the type is", then  
>we have no disagreement. I said from the begining that the downcast should only  
>be allowed when you already know that the cast is on the exact type you want.  

	But you dont know the type without asking the object.
	You are nowhere required to specify which downcasts you might do.

>> 	I am looking at a constraint where all the things that
>> a function can downcast to must be listed (or implied by the list)
>> in the interface.
>> 
>> 	f(Base[Derived1,Derived2]*);
>> 
>> 	This mechanism has some nice features, although I am
>> not sure about its soundness yet.
>>
>Are Derived1 and Derived2 sealed classes? Or is it true that the pointer  
>"Base[Derived1,Derived2]*" can only accept exactly two derived classes ( no  
>derived classes of Derived1 or Derived2)? 

	It will accept ANY pointer that can convert to a pointer
to Base. It is just like  Base*, with explicit permission to
cast to Derived1 and Derived2. (And ANY of their base classes,
since you can then upcast)

	But please note: this proposal as it stands is just as
flawed as the original downcasting because the thing is not
a real type. It makes the interface explicit:

	f(B[D1,D2]&);

but only for f, nothing would stop you circumventing this by

	g(B& b){ f(b); }


>IF NOT, such constraint helps nothing  
>in RTTI and downcast. Let me suppose YES, it still helps little (see below)
> 
>> 	Member functions can always downcast to their class.
>> That immediately implies a 'thisclass' facility, and with no
>> additional syntax in the declaration. 
>>
	What I meant by this is that

	dog::mate(animal&)

implies

	dog::mate(animal[dog]&);

without you needing to write it.

>  
>> 	Furthermore, explicit downcasting operators are not 
>> required always, since the declaration already lists the legal
>> downcasts.
>>
>True, but not by your reason. 

	I refer to Bjarne's proposal: it requires explict downcasts.
(To the best of my knowledge)

>Example,
>
>    Animal a1 = new Cat;
>    Animal a2 = new Dog;

	But this is illegal syntax!

>    Cat c = *a1;
>    Dog *d = a2;
>
>No explicit RTTC required here. 
>Both you and the complier know these downcasts  
>are correct and safe;

	Yes, but why would you do something that stupid?
Why would you not do it the other way:

	Cat c*=new Cat;
	Animal * a=c;

>
>You proposed a constraint through a fixed set of *exact* types(I suppose so).  

No, the constraint is that you can only downcast to the listed types,
or those implied by that (via subsequent upcasts).

>What is the purpose of such contraint? It cannot eliminates or even reducing  
>the necessity of run time type check. 

	The purpose was to make the donwcastability explicit
in the interface. As I pointed out, as it stands, it fails to even
do this.

	And I say again: I am NOT against using compiler generated RTTI,
per se. 


>Non-derterminisim between the two and  
>among the infinitive both require run time type check. 

	Yes, but it CAN be done without RTTI in the object:
the information can be put into the pointers instead. 
This seems to be BETTER than RTTI in the object, because
there is no restriction on the need for virtual functions,
indeed the objects wouldn't even have to share a common base.
This is precisely what's needed for het aggregates of finite
different types. The only problem is that its not extensible.

	The extensibility may HAVE to be sacrificed in return
for soundness. If this were not the case, then you could
just use a virtual function in the first place.
>
>> 	Even better, the syntax might support casting to unrelated
>> types, and can be implemented then, and when no virtual functions
>> are available, by using tagged pointers or some other mechanism.
>>
>A complete RTTI should already support casting from a set of unrealted types to  
>a concrete type. It has nothing to do with any constraint.

	The implementation is currently considered a 'constraint'.

>
>> 	Finally, for the moment, one can implement
>> 
>> 	f(Base[Derived1, Derived2]* b)
>> 	{
>> 		...
>> 		type switch(ab) {
>> 		case Derived1:
>> 			b->Derived1_member();
>> 		case Derived2:
>> 			b->Derived2_member();
>> 		default:
>> 			b->Base_member();
>> 		};
>> 	}
>> 
>> although there would have to be an implied break after each case.
>
>No default case needed here in the type switch because the interface of "f"  
>already tells you that there are only two cases of the input type. 

	That was NOT the intention. 

	In a heterogenous collection, the idea was that 
a new type and new routine added might say:

	f(Base[NewType]*);

It then can downcast to NewType (which it knows about). But the 
collection STILL contains objects of types UNKNOWN to IT.
These are the defaults.

>This is the  
>reason we need constraint: to help the resulting implementation more correct.  
>While "thiscalss" describes a covariant constraint of dependent types, tagged  
>union types (what you proposed is actually a kind of tagged union type as far  
>as I can understand) desribes a constraint of independent types. They cannot be  
>replaced with each other.

	You can use the constrained downcasting to implement
"thisclass".  It leaves the problem of covariance up to the programmer.
And there is no run-time overhead unless it is used.

	virtual animal::mate(animal&);

	dog::mate(animal& a) // Implies mate(animal[dog]&);
			   // note: NOT COVARIANT.
	{
		... code using animal only, might return
		dog other&=a; // exception on type error
		.. code using COVARIANCE--dog
	}

So its up to the programmer. MORE flexible than a mere covariant type,
and sound too.


-- 
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
	Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Technical Details
Message-ID: <1992Aug2.121522.19096@ucc.su.OZ.AU>
Path: arc1-ca.pugleaf.net!sparky!uunet!munnari.oz.au!metro!extro.ucc.su.OZ.AU!maxtal
References: <1992Jul30.172830.18501@ucc.su.OZ.AU> <1992Jul31.230135.1201@cadsun.corp.mot.com>

Show Full Headers
Complete Article Headers
Newsgroups: comp.std.c++,comp.lang.c++
Path: arc1-ca.pugleaf.net!sparky!uunet!munnari.oz.au!metro!extro.ucc.su.OZ.AU!maxtal
From: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Subject: Re: run-time type checking (was: Re: Covariant Types in Derived Classes)
Message-ID: <1992Aug2.121522.19096@ucc.su.OZ.AU>
Sender: news@ucc.su.OZ.AU
Nntp-Posting-Host: extro.ucc.su.oz.au
Organization: MAXTAL P/L C/- University Computing Centre, Sydney
References: <1992Jul30.172830.18501@ucc.su.OZ.AU> <1992Jul31.230135.1201@cadsun.corp.mot.com>
Date: Sun, 2 Aug 1992 12:15:22 GMT
Lines: 258