From: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Subject: Re: O.M(...) vs M(...), and is the Real World O-O?
Organization: MAXTAL P/L C/- University Computing Centre, Sydney
Date: Sun, 4 Oct 1992 15:06:50 GMT

In article <1992Oct2.200803.6710@netcom.com> Bob Hathaway <objsys@netcom.com> writes:
>In article <1992Sep27.140932.2792@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
>>>If the downcast fails,
>>>all is well because f won't be able to access a Base's protected members.  In
>>>other words, all is well because downcasting obviously enforces interface
>>>access specifications.
>>
>>	I do not agree that it is ok to access the interface
>>of an object based on its actual type. The correct method for accessing
>>members based on the run-time type of an object is by virtual functions.
>
>This is only one (quite restricted) way.  Smalltalk provides the opposite
>by allowing any method to be passed.  There is a middle ground and I think
>that should be preferred.  I suggest reading up on some dynamically typed
>OO languages for breadth. 

	I'm aware of several. I'm not against them. Smalltalk provides
this power at a huge price--correctness. It is not type safe. 
C++ is. But then, Smalltalk is object oriented, whereas C++
is really class oriented.
>
>>	When I create and abstract base class, and then write routines
>>which manipulate that base class, I expect those routines to be
>>able to manipulate the objects via the abstract interface only.
>>A routine so written will work for ANY derived class (provide that
>>derived class has the proper semantics), and that is the basis
>>of polymorphism.
>
>That is the basis for a limited variety of polymorphism; I think you're
>leaving out the most important and general cases.  Many environments
>*require* more than the statically typed variety.

	None I've seen, but they're only things like non-object
oriented operating systems, which dont require any polymorphism
at all. Of course the Smalltalk environment requires the sort
of thing you're talking about, which is hardly surprising.
>
>>>And using virtuals assumes all functions of all derived
>>>classes are moved into the base class.  This is most undesirable with the
>>>most obvious reason being an unnecessary coupling of Base and Derived
>>>classes (and there are many more).
>>
>>	If you get this problem (fat interface) you have a lousy design.
>>And you're still thinking in terms of single inheritance. Anyone
>
>No, my point applies to MI as well.  And you're ignoring the most
>important cases again.  I think you've grasped the C++ virtual function
>mechanism well but I suggest reading up on something like Coplien
>to cover the rest; I think you're about 1/4 the way there...

	I've read Coplien. Several times (grin) and dont profess
to understand every idiom. I actually asked Coplien about the
"mixin" idiom that I prefer, and he said he hadn't really considered
it. The thing is he, like many readers here, came from a background
of using dynamic systems like Lisp and Smalltalk, and try
to emulate these features in C++, complaining when it is hard or
even possible only with kludges. C++ deliberately doesn't let
you do these things because they compromise system integrity.

	The issue of fat base classes is a consequence of
single inheritance. Smalltalk uses single inheritance.
Small wonder Smalltalk programmers using C++ want
to have a class 'Object" and derive everything from it,
then downcast to retrieve the real object. But C++
has quite the opposite philosophy from Smalltalk, and almost
no-one has understood it.

	1) Use of MI is still being disputed in the academic journals
	2) There are hardly any published examples

>
>>>Not at all.  For example, a device class can be declared.  Certain kinds of
>>>devices may have certain operations and certain kinds may not.  Downcasting
>>>is *one* way of accessing these derived operations without moving all derived
>>>class functions into the base class as virtuals.  Another solution is to
>>>dynamically query if a message is available in a polymorphic object and only
>>>invoke it if it is; this is common in Objective-C.
>>
>>	And BOTH of these would be bad design for C++ in my books.
>>In C++ you have to know the interface you intend to use statically.
>>Only the implementation is allowed to be dynamic (via virtual
>>functions).
>
>Again, Coplien.  There are many general cases where this kind of flexibility
>and greater power are required.  Try not to get stuck on just what the C++
>language provides as primitives since these were only added to C++ if
>they didn't add any non-trivial overhead and not because they were the required
>or most desirable facilities.  Coplien covers many cases in which C++ is
>intended to be flexible enough to cover but only with extended "idioms".

	No argument. Nothing wrong with idioms. But downcasting 
is not an idiom. To add checked downcasting to C++ requires
a run-time system. It is a major language extension.
C++ does not have a run-time system at the moment,
except for 'new' and 'delete'. When exceptions are added, RTTI
will be needed and its going to cause some problems. Because
it represents coupling not explicit in the interfaces. But at least
the programmer cant get at it.

>
>>	If you have, for example a base class 'stream' device driver,
>>and then you have 'serialport' and 'diskdrive' then it is true
>>that for example disk files can be 'rewound' to the start,
>>whereas serial ports can have their baud rate changed.
>>
>>	If you write a routine using 'stream' it must not depend
>>on the specific device type. ONLY on the attributes of all
>>streams. That is called device independence.
>
>Ok, good case for abstract classes (or virtuals).  I didn't mean to infer
>this case doesn't exist, just that it doesn't cover the entire spectrum .

Really? But this is an example of a foreign interface, one of the
types of thing that might actually benefit from downcasting.
(At least it is a candidate for it).
>
>>	Suppose you downcast to 'serialport' or 'diskdrive',
>>so as to, say open the stream correctly. Thats ok in
>>your books isnt it?
>
>No, in this case you would only use the base interface.  Placing the classes
>inside of a container which defines the only public interface is a solution to
>this problem (the envelope/letter idiom), at least without direct language
>support.  Just because more powerful facilities are added to a language doesn't
>mean the less powerful facilities are no longer useful, they simply provide
>greater facility when needed (which is not in this case).

	Oh, Ok. The issue here is that I contend that the extra power
downcasting supplies is not worth it in terms of security. To give
another example, suppose that someone proposed a 'struct'ing
operator that exposed all of a classes private data members.

	That is indeed a powerful facility. I doubt you'd approve though.
The argument against downcasting has a similar structure, it
provides power at the expense of certain guarrantees, and the issue
is whether it is worth it or not.

	One could argue (not quite seriously) that C++ offers
FOUR protections levels: public, protected, private and hidden.
Hidden are the members that are so well protected you cant even
see them. They should be protected even more strongly than private
variables. To expose them would be mortal sin. :-)

(Of course the non-virtual members of a derived class are hidden 
from the base class ... and downcasting exposes them)

>
>>	But you just wiped device independence. Because now
>>I add 'video' to the devices, and your routine fails.
>>In by books, sticking to the declared interface
>>guarrantees polymorphism.
>
>But precludes more demanding applications.  

	WHAT more demanding applications?  

>Just use the facilities as
>appropriate and you avoid problems.  Again, the envelope/letter idiom
>can be trivially used to support this enforced idiom, so do you still
>profess there should be language enforcement?  I think there must
>be the alternative of greater flexibility which leaves the choice between
>adding language support for strict base specification access or just
>settling for idioms.

	I'm sorry for not understanding. Did I miss a downcast
in the envelope/letter idiom? Without downcasting, what I require
is ALREADY enforced in C++. There already IS that strict enforcement.
(Unless you do an *unchecked* cast---and if you use virtual 
inheritance that is illegal)
>
>Downcasting must be statically declared in advance too, I don't see your
>point.

	Sorry. I have a proposal to add tagged pointers to C++ like this:

	[A,B,C] *p; // p is a pointer to A, B, or C

It can be used for 'heteromorphism', especially aggregates like 
lists or arrays. The implementation is the equivalent of
a union of A*, B*, C* with a tag to select the correct type.
In use like

	p->print();

the compiler generates code for all three cases and selects the correct
one using a 'switch'. There is a course a way of doing this using
an idiom (the tagged pointer idiom), but it is messy and
accident prone. 

	Given also

	[B,C,D]* q;

	p=q; // compile time error

In other words, the system is statically sound, no run-time
errors can be generated.

Downcasting requires you know the name of the class you want to
downcast to, and thats all. It allows you to access an object
by the interface it was created with, and not merely the
interface that it is declared with. The function doing
the downcasting does not have to 'predeclare' that it is
going to downcast---it can downcast to any class it knows
the name of.

>
>>Tagged pointers are completely statically type safe and sound.
>>Checked downcasting is safe, but not IMHO sound.
>>
>>	Tagged pointers are used to implement heteromorphism,
>>which is the opposite of polymorphism. Hetermorphism manages
>>similar looking interfaces compactly, polymorphism requires identical
>>interfaces.
>
>Why are you claiming this?  Smalltalk has *no* interface enforcement
>but is still polymorphic, in fact even much more so than in statically
>typed casses.  Could you explain "heteromorphism"?

	If I pass a message X to an object in Stalk, the X message
of that object is executed if it has one, or a run-time error results.
Is this right (please correct me if I make a mistake).

To the limited extent that having a method X is an part of the 
objects interface, Stalk surely *does* enforce it. However,
in Stalk there is no distinction between the actual methods
of an object and the declared methods, as there is in C++
or Eiffel. So Stalk *cant* enforce the declared interface,
because there isnt one.

SO all systems either enforce the interface or they crash,
either at compile time or run-time or both. At compile time
is far better, for obvious reasons.

I claim that in a domestic program (one that creates and destroys
all the objects and manages them itself without  giving up
control of them to an external interface) downcasting is not required.

Heteromorphism means "different structure". Homomorphism
means "same structure". Polymorphism means "many structured".

In C++ polymorphism comes with a guarrantee that cant be made in Stalk.
If I write a routine f(Base*) that works, and I have an arbitrary
class Derived derived from Base (which is also a subtype of Base)
then f will work with Derived* too.

If downcasting is allowed, I dont believe this will be true anymore.
>
>Certain applications *require* the flexibility of dynamics, in other
>words there are domains where static typing doesn't work and you have
>no choice.  The two are not mutually exclusive.

	Perhaps there are. Show me a nice small one.

>
>>>>	class Q {
>>>>	public:
>>>>		void enQ(X&);
>>>>		X deQ();
>>>>		int nQ()const;
>>>>	};
>>>>
>>>>A queue manager routine has calls
>>>>
>>>>	getsome(Q&); .... putsome(Q&);
>>>>
>>>>where 'getsome' and 'putsome' are user supplied source and sink routiines.
>>>>Now the Q manager is supposed to preserve the order of things in the queue.
>>>>But as it stands, getsome or putsome might scramble the order. So we
>>>>change the Q class---
>>>>
>>>>	class Qbase { public: int nQ()const ...}
>>>>	class NQ : public virtual Qbase { public: enQ(X&); };
>>>>	class DQ : public virtual Qbase { public: X deQ(); };
>>>>	class Q : public virtual NQ, public virtual DQ {};
>>>>
>>>>Now I can declare
>>>>
>>>>	getsome(NQ&);
>>>>	putsome(DQ&);
>>>>
>>>>and, without downcasting these routines cannot scramble the Q.
>>>>With downcasting, they can.
>
>Ok, now I see the point.  Envelope/letter works just fine here by avoiding
>inheritance in the choice of specification 

	I'm not saying the above couldn't be done another way
which made it more secure. But I'm saying that C++ was designed
to be used precisely in the sort of way I've shown above.
'Upcasting' is a sort of 'forgetful functor' and its useless
as a protection mechanism if you can undo it.

>but this is a valid argument
>for including some kind of "enforced" interface.  This does not infer;
>however, that downcasting or non-strict interfaces should never be used
>since I have alluded to many cases in which it should.

	Actually, I agree, but I'd put it the other way around.
I think that *unconstrained* downcasting is naughty, that is,
it is unsound. SO I search for appropriate constraints.

	Tagged pointers show one very restrictive constraint
that could be applied (except they do more than downcasting
in other areas). Another constraint would be to list
all the derived class that you can downcast to in the base class.
(Whicu I think is a weaker form than the tagged pointers).

None of these have the single advantage of downcasting to
cast down to any old class you like. But that voids other
assurances. I dont like that, so I want to see some
examples that require downcasting.

I might even come up with some myself. I'm arguing against
downcasting not because I'm committed against it, but
because I'm being conservative with the few precious correctness
assurances that C++ does offer.

I have to admit that to me downcasting just feels like an almighty
hack, and will be used by slack programmers to avoid proper design.
But I admit that I've used one in my singly linked list class !
[At least here I didnt have to, I just did it for code reuse.
Thats my excuse, but I'll have to redesign the class]

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