From: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Subject: Re: Downcasting (was: Re: run-time type checking)
Organization: MAXTAL P/L C/- University Computing Centre, Sydney
Date: Sat, 15 Aug 1992 17:01:41 GMT

In article <4823@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>: jbn@lulea.trab.se (Johan Bengtsson) writes:
>: 
>: >maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
>: >:
>: >:	class Base { //....
>: 		virtual operator Derived1*() { return 0; }
>: 		virtual operator Derived2*() { return 0; }
>: >:	};
>: >:Now what does this do? Well, it preserved th open/closed principle
>: >:and encapsulation and the explicit interfaces principle.
>: >
>: >What if someone using your classes decides to add a Derived3 class
>: >to which downcasting should be possible?  He must modifiy your
>: >class Base, for which he may not have source code.
>: 
>: Yes, but that would be a change to the interface!
>
>	I get your point.  I also agree that the above technique
>	is best (most efficient) when you have a limited set
>	of known derived classes.

	No one here is arguing about efficiency.
	The set of derived classes is ALWAYS finite in a domestic
	program.

	The word 'known' is the keyword.
>
>: 
>: The whole point is that the ability to downcast a Base to a derived class
>: SHOULD be part of the interface. If you want to change the interface, then
>: of course you are going to need to modify the source code - this does not
>: violate the open/closed principle.
>
>	I agree that unrestricted typecasting provides the ability to
>	cleanly extend the interface of a base class, into the interface
>	of any derived class.  Although I admit that in a way this does
>	break the "closedness" of the Base class interface, I don't think
>	it does so in a "bad" way.

	This is the issue. You might be right, but I'm not convinced.
In a statically secure system, I argue that it is important to access
an object only by its explicitly declared accessible interface.
	C++ already prevents you accessing explictly declared
and visible *inaccessible* members (private ones, for example).
To raise the accessibility of *invisible* members above that
of visible but inaccessible members seems to be dangerous.
>
>	After all, inheritance allows us to break the "closedness"
>	of a base class' behaviour, by overriding virtual functions.

	No it does not. The base is BOTH open and closed, and this
is the open/closed principle. In that C++ does not provide
complete semantic checking (no other language does either),
it is possible but wrong to change the semantics of a class
via a virtual function so the 'isA' relationship of derived to base
is violated. 

	The ability of a module to be closed and also open
is what make polymorphism possible, and conversely, breaking
the closedness will destroy the openess, that is, polymorphism.

	Polymorphism offers an assurance that we are free
to ARBITRARILY derive new classes from a base, and any or
none of these classes may be substituted for the base.
(References or pointers at least).

	As a result it is possible to write functions f(Base&)
that work correctly not only on base objects, but ANY object
derived from base.

	If the function f(Base&) calls a function g(Base&)
which silently performs special actions if the reference
is to some PARTICULAR derived class, then changes to
the object can be made NOT conforming to the assumed
invariants of the base, and so f may fail to operate
correctly.

	I will try to construct an example.

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

	f(X& a){ int n=a.get(); g(a); assert(n==a.get());}

I would expect the assertion to obtain because class X provides
no public mechanism to modify 'x', and f has been granted and grants
to g only public access to the X object a.
		
	class Y : public X {
	public:
		Y(int i) : X(i) {}
		void set(int i){x=y;}
	};

	Y aY(3);
	f(Y);

Now the assertion may fail if 'g' downcasts to Y.
IMHO this is 'bad' because it means in effect
that the protected interface is public. Closure is broken
because the separately compiled routine f now needs to
know of Y if g does. In effect to maintain integrity,
f must ALSO check if the object is of type Y, which
means that it cannot be closed until g is, defeating
the point of separate compilation-- in effect a function
f cannot depend on a function g to conform to its 
declared interface.
>
>	The importance is that in neither case do you have to
>	intervene with the source or compiled code of the base
>	class to do what you want.  This is considered good
>	for behaviour (overriding virtuals), so why not also
>	for interfaces (unrestricted but safe downcasting)?

	It is sound for virtuals because they are declared!
They are there PRECISELY to allow this ability. Because the
base is closed and open one can have arbitrary derived classes,
and should be able to DEPEND on this fact. It is USEFUL
to know that arbitrary derivation can occur. The arbitrariness
is a powerful constraint--you may not depend on any particular
derived class behaviour. As a consequence, you MAY depend on
all the objects behaving withing the constraints of the base
class interface.

	The ability to downcast implies that the protected
interface of a base class is not protected, it might just
as well have been made public.
>
>	To conclude:
>
>	A base class intended for future inheritance (even by
>	other programmers) should support overriding behaviour
>	using virtual member functions, and should support
>	interface extension by runtime-checked, safe downcasting.
>
>	This lessens the risk that future users without access to the
>	source code will get stuck when deriving from the base class.


	No, what it does is opens the possibility for programmers
to destroy the functionality of existing working and closed
routines by misunderstanding what polymorphism is and using it
instead for heteromorphism. The point of polymorphism is that
your should not rely on derived class behaviour in routines
given a base object, and you shouldn't need to.

	An derived object which 'isA' base  is not merely
'a bit like a base', but IS precisely a base--no more, no less.

	When inheritance is used to add new members only, that
is equivalent to composition.

	When the two techniques are combined we have a mix
of polymorphism and heteromorphism. The two should be clearly
separated. Tagged pointers provide one way to do this.
Downcasting blurs the issue: it pretends downcasting
is a mechanism associated with polymorphism, and in so doing
destroys it.

-- 
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
	Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
From: ravinc@hpdmd48.boi.hp.com (Ravin Chauhan (temp))
Date: Thu, 20 Aug 1992 20:21:21 GMT
Subject: Strange problem with Borland C++
Organization: Hewlett Packard - Boise, ID