[comp.lang.c++] typesafe downward casting

kearns@softrue.UUCP (Steven Kearns) (03/29/91)

There has been much discussion lately about adding stuff to C++
so that people can assign a Base* to a Derived* without a cast.  
People feel "guilty" when they use a cast, as if they are doing something
unsafe.

If you look at things from a "program verification" viewpoint, the issue
becomes much clearer.  A cast of (Base*) to Derived* is just a function
call that assumes that the Base* really points to a Derived*;  if you
can prove to yourself (and God) that this is so, then using the cast
is perfectly safe, and you should not feel guilty.  

If we added stuff to C++ to take care of this "problem", then we would
also have to add new types so that people do not call sqrt(x) when
x is a negative number, or so they do not call a/b when b is 0.  

-steve

********************************************************
* Steven Kearns            ....uunet!softrue!kearns    *
* Software Truth           softrue!kearns@uunet.uu.net *
********************************************************

cok@islsun.Kodak.COM (David Cok) (03/31/91)

In article <17.UUL1.3#8618@softrue.UUCP> kearns@softrue.UUCP (Steven Kearns) writes:
>There has been much discussion lately about adding stuff to C++
>so that people can assign a Base* to a Derived* without a cast.  
>People feel "guilty" when they use a cast, as if they are doing something
>unsafe.
>
>If you look at things from a "program verification" viewpoint, the issue
>becomes much clearer.  A cast of (Base*) to Derived* is just a function
>call that assumes that the Base* really points to a Derived*;  if you
>can prove to yourself (and God) that this is so, then using the cast
>is perfectly safe, and you should not feel guilty.  

And if you can prove that global variables are used correctly, and goto's, and
case statements or types, you should not feel guilty???  Some constructs are
error-inviting -- cast is one of them -- and are best avoided.  If you need
to use them, you have to be very careful, but if a program has a lot of them
bugs are likely.  Current C++ can hardly be used for some kinds of things
(at least the alternatives to casting are just as bad) without a lot of
Base* to Derived* casts.  It would add to the robustness and strong-typing
nature of the language to have type-safe downcasts.
>
>If we added stuff to C++ to take care of this "problem", then we would
>also have to add new types so that people do not call sqrt(x) when
>x is a negative number, or so they do not call a/b when b is 0.  
>
No, you don't have to.  Every language is a trade-off between what is checked
at compile-time, what is checked at run-time, and what is left to cause core
dumps, often at places far from the original problem.  People differ as to
what should be in which category -- witness the whole static vs. dynamic
typing debate.  Numeric range problems have traditionally been left to either 
fault (at least at the location where they occur), or to cause a run-time 
exception.  I think that a type-safe downcast is a legitimate thing to consider
supporting at run-time.

>-steve
>
>********************************************************
>* Steven Kearns            ....uunet!softrue!kearns    *
>* Software Truth           softrue!kearns@uunet.uu.net *
>********************************************************

David R. Cok
Eastman Kodak Company -- Imaging Science Lab
cok@Kodak.COM

kearns@softrue.UUCP (Steven Kearns) (04/02/91)

> From: cok@islsun.Kodak.COM (David Cok)
> Subject: Re: typesafe downward casting
> ....
>And if you can prove that global variables are used correctly, and goto's,and
> case statements or types, you should not feel guilty???  Some constructs are
> error-inviting -- cast is one of them -- and are best avoided.  If you need
> to use them, you have to be very careful, but if a program has a lot of them
> bugs are likely.  

Yes, if you can prove that your goto is correct, you should feel very
satisfied.  The only reason to avoid these statements, as Djikstra
would say, is because they complicate the proof of correctness.  It
seems to me that knowing that a Base* actually points to a Derived*
is about as trivial as knowing that a number is non-negative.  If you
cannot handle this, I suggest taking up a different profession ;-}.

> >If we added stuff to C++ to take care of this "problem", then we would
> >also have to add new types so that people do not call sqrt(x) when
> >x is a negative number, or so they do not call a/b when b is 0.  
> >
> No, you don't have to. Every language is a trade-off between what is checked
> at compile-time, what is checked at run-time, and what is left to cause core
> dumps, often at places far from the original problem.  People differ as to
> what should be in which category -- ....
> I think that a type-safe downcast is a legitimate thing to consider
> supporting at run-time.

That is the point: there is no compelling reason to include it in the
language except for your opinion.  Why make type-safe downcasts but
non-type-safe division?  Fortunately, C++ gives you enough tools so
that you can ensure type-safe downcasting if you want, while I can
avoid them if I want.  

-steve

********************************************************
* Steven Kearns            ....uunet!softrue!kearns    *
* Software Truth           softrue!kearns@uunet.uu.net *
********************************************************

cok@islsun.Kodak.COM (David Cok) (04/03/91)

In article <18.UUL1.3#8618@softrue.UUCP> kearns@softrue.UUCP (Steven Kearns) writes:
>> From: cok@islsun.Kodak.COM (David Cok)
>> Subject: Re: typesafe downward casting
>> ....
>>And if you can prove that global variables are used correctly, and goto's,and
>> case statements or types, you should not feel guilty???  Some constructs are
>> error-inviting -- cast is one of them -- and are best avoided.  If you need
>> to use them, you have to be very careful, but if a program has a lot of them
>> bugs are likely.  
>
>Yes, if you can prove that your goto is correct, you should feel very
>satisfied.  The only reason to avoid these statements, as Djikstra
>would say, is because they complicate the proof of correctness.  It
>seems to me that knowing that a Base* actually points to a Derived*
>is about as trivial as knowing that a number is non-negative.  If you
>cannot handle this, I suggest taking up a different profession ;-}.
>
No, you should feel satisfied only if it is correct and is a good design.  One
measure of goodness of design is the speed with which a person unfamiliar
with the code can justify its correctness -- and gotos generally, but not
always, make that harder.  Global variables require being aware of a lot more
information than local variables -- and hence complicate (slow down) verifying
correctness.  I would maintain that uncheckable casts fall into the same
category, and that a good program generally has few casts and a good 
programming language allows most programs to be written without casts.
Knowing that a Base* points to a Derived* is about as trivial as knowing
that a number is non-negative: both are undecidable in general at compile-time.
At least C++ provides a nice little run-time test to see if a number is 
non-negative; it does not provide that for casting (except in a messy way -- 
see below).

>> >If we added stuff to C++ to take care of this "problem", then we would
>> >also have to add new types so that people do not call sqrt(x) when
>> >x is a negative number, or so they do not call a/b when b is 0.  
>> >
>> No, you don't have to. Every language is a trade-off between what is checked
>> at compile-time, what is checked at run-time, and what is left to cause core
>> dumps, often at places far from the original problem.  People differ as to
>> what should be in which category -- ....
>> I think that a type-safe downcast is a legitimate thing to consider
>> supporting at run-time.
>
>That is the point: there is no compelling reason to include it in the
>language except for your opinion.  Why make type-safe downcasts but
>non-type-safe division?  Fortunately, C++ gives you enough tools so
>that you can ensure type-safe downcasting if you want, while I can
>avoid them if I want.  
>
But there is no compelling reason to leave it out except for your opinion :-)
I'll take "opinion" here to be a position on language design based on some
rational considerations -- on which reasonable people can disagree and can
discuss without resorting to ad hominem arguments.

Current C++ can supply type-safe downcasting only with (a) source code
access to base classes, including libraries, and (b) building into base
classes knowledge of each derived class.  I maintain that each of those 
individually is too big a price to pay for what is a straightforward
language feature.


David R. Cok
Eastman Kodak Company -- Imaging Science Lab
cok@Kodak.COM

chip@tct.com (Chip Salzenberg) (04/04/91)

According to cok@islsun.Kodak.COM (David Cok):
>I would maintain ... that a good program generally has few casts and a
>good programming language allows most programs to be written without casts.

Indeed.  And that is true of C++ today -- IF you are willing to avoid
using Base* all over the place when Derived* is meant.  (Template
collections are a tremendous help.)

In other words, the way to regain an object's lost static type is not
to lose it in the first place.
-- 
Chip Salzenberg                    <chip@tct.com>, <uunet!pdn!tct!chip>
  Brand X Industries Custodial, Refurbishing and Containment Service
            When You Never, Ever Want To See It Again [tm]

cok@islsun.Kodak.COM (David Cok) (04/05/91)

In article <27FA1EC4.511C@tct.com> chip@tct.com (Chip Salzenberg) writes:
>According to cok@islsun.Kodak.COM (David Cok):
>>I would maintain ... that a good program generally has few casts and a
>>good programming language allows most programs to be written without casts.
>
>Indeed.  And that is true of C++ today -- IF you are willing to avoid
>using Base* all over the place when Derived* is meant.  (Template
>collections are a tremendous help.)
>
>In other words, the way to regain an object's lost static type is not
>to lose it in the first place.
>-- 

I am certainly looking forward to having an environment that supports templates
(no such luck here yet), and think it will do great things for reusing 
implementations of Lists, etc., but I don't think it is a replacement for
inheritance.  

If you use inheritance simply to provide a new implementation of a class, you
can keep manipulating the Base* values, and never need to deal with the
Derived class at all except to state in a declaration which derived class to
use, e.g.  List* list = new MySpecialListImplementation.

But another use of inheritance is to add functionality to an existing class by
deriving from it.  Now virtual functions which returned Base* will still
return Base* in the derived class -- so I cannot help losing the object's
static type.  In this context one must either have contravariance on the
return type of virtual functions or type-safe down casting -- or resort to
current C++ which provides only type-unsafe down casting or corrupting the base
class with the interface to the added functionality of the derived class.

David R. Cok
Eastman Kodak Company -- Imaging Science Lab
cok@Kodak.COM

daves@ex.heurikon.com (Dave Scidmore) (04/07/91)

cok@islsun.Kodak.COM (David Cok):
>I would maintain ... that a good program generally has few casts and a
>good programming language allows most programs to be written without casts.

Since C, and for the most part C++ as well, are not strongly typed languages
and rely on casts to allow the programmer to make explicit changes in type,
I would disagree with the above statement. Automatic or built in changes of
type are essentially hidden rules which rely on the programmers intimate
knowledge of the type structure and possibly even an intimate knowledge
of the program  to understand what is really happening.

As a rule, I tend to like people who write programs I might be required to
maintain to make as explicit as possible any subtle changes of type which
I might not immediately grasp on first examination of the code. Casts are
a means of making clear to other programmers what the original programmer
intended. In the absence of casts when hunting down a bug I must constantly
ask myself if the original programmer intended the "hidden" type conversion
to occur, or whether the programmer was unaware that the type conversion
could be taking place. When a cast is provided I have a much higher degree
of confidence that what I see happening in the code was what was actually
intended.

In other words the authors of C provided casts as an alternative to the
straight jacket form of type checking that Pascal has. Attempts to provide
"hidden" type conversions can easily be used to erode that form
of type checking the language provides. Erosion of that type checking
provides more pitfalls into which programmers can fall.
--
Dave Scidmore, Heurikon Corp.
dave.scidmore@heurikon.com

chip@tct.com (Chip Salzenberg) (04/08/91)

According to cok@islsun.Kodak.COM (David Cok):
>I am certainly looking forward to having an environment that supports
>templates (no such luck here yet), and think it will do great things
>for reusing implementations of Lists, etc., but I don't think it is a
>replacement for inheritance.

That's a straw man; no one has suggested otherwise.

>If you use inheritance simply to provide a new implementation of a class, you
>can keep manipulating the Base* values, and never need to deal with the
>Derived class at all except to state in a declaration which derived class to
>use, e.g.  List* list = new MySpecialListImplementation.

That's my most common use.

>But another use of inheritance is to add functionality to an existing class by
>deriving from it.  Now virtual functions which returned Base* will still
>return Base* in the derived class -- so I cannot help losing the object's
>static type.  In this context one must either have contravariance on the
>return type of virtual functions or type-safe down casting ...

Or (another choice): modify the base class for the added functionality
(add a do-nothing virtual function, etc).  That's my choice.
-- 
Brand X Industries Custodial, Refurbishing and Containment Service:
         When You Never, Ever Want To See It Again [tm]
     Chip Salzenberg   <chip@tct.com>, <uunet!pdn!tct!chip>

cok@islsun.Kodak.COM (David Cok) (04/09/91)

In article <28007BC8.D71@tct.com> chip@tct.com (Chip Salzenberg) writes:
>According to cok@islsun.Kodak.COM (David Cok):
	....
>
>>If you use inheritance simply to provide a new implementation of a class, you
>>can keep manipulating the Base* values, and never need to deal with the
>>Derived class at all except to state in a declaration which derived class to
>>use, e.g.  List* list = new MySpecialListImplementation.
>
>That's my most common use.
>
>>But another use of inheritance is to add functionality to an existing class by
>>deriving from it.  Now virtual functions which returned Base* will still
>>return Base* in the derived class -- so I cannot help losing the object's
>>static type.  In this context one must either have contravariance on the
>>return type of virtual functions or type-safe down casting ...
>
>Or (another choice): modify the base class for the added functionality
>(add a do-nothing virtual function, etc).  That's my choice.
>-- 

You left off the rest of my statement:

>> ... or corrupting the base class with the interface to the added 
>> functionality of the derived class.

I just can't see that modifying the base class is a generally reasonable
alternative -- even if you do have source code access, which in general you
do not.  Why should the added functionality of derived classes have to be
propagated up the inheritance tree?  That seems to violate all principles
of modularity in designing classes, causes excessive code bloat and puts
all derived class function names in the same name space.

David R. Cok
Eastman Kodak Company -- Imaging Science Lab
cok@Kodak.COM

Ari.Huttunen@hut.fi (Ari Juhani Huttunen) (04/09/91)

In article <28007BC8.D71@tct.com> chip@tct.com (Chip Salzenberg) writes:

>>But another use of inheritance is to add functionality to an existing class by
>>deriving from it.  Now virtual functions which returned Base* will still
>>return Base* in the derived class -- so I cannot help losing the object's
>>static type.  In this context one must either have contravariance on the
>>return type of virtual functions or type-safe down casting ...

>Or (another choice): modify the base class for the added functionality
>(add a do-nothing virtual function, etc).  That's my choice.

Is it still your choice if the base class is provided only in binary form?
For example, a commercial library of classes for which you have no source
code, except the header files.
--
Ari Huttunen, email: Ari.Huttunen@hut.fi, phone: 90-7285944

chip@tct.com (Chip Salzenberg) (04/11/91)

According to Ari.Huttunen@hut.fi (Ari Juhani Huttunen):
>>Somebody writes:
>>>But another use of inheritance is to add functionality to an existing
>>>class by deriving from it.  ...  In this context one must either have
>>>contravariance on the return type of virtual functions or type-safe
>>>down casting ...
>
>In article <28007BC8.D71@tct.com> chip@tct.com (Chip Salzenberg) writes:
>>Or (another choice): modify the base class for the added functionality
>>(add a do-nothing virtual function, etc).  That's my choice.
>
>Is it still your choice if the base class is provided only in binary form?

If I only have binaries, I refuse to inherit.  I compose instead.
It saves everyone a lot of trouble.
-- 
Brand X Industries Custodial, Refurbishing and Containment Service:
         When You Never, Ever Want To See It Again [tm]
     Chip Salzenberg   <chip@tct.com>, <uunet!pdn!tct!chip>