[comp.lang.c++] Casting downward

dougo@soda.berkeley.edu (Doug Orleans) (05/28/91)

Recently I began my first major C++ programming project.  I am using
Turbo C++ 1.0 on an XT clone.  Turbo C++ comes with a fairly extensive
class heirarchy, so I decided to use that as a base.  I ran up against
a major problem, however, when I tried to use multiple inheritance,
and I came up with two solutions: one is a kludge, and the other
involves a change in the language definition.

My problem was this:  there is a class Object which lies at the root
of the heirarchy.  One branch of the tree is the Container branch,
which has classes such as Container, Collection, and Array.  Another
branch is the Sortable branch, which has classes of objects which can
be sorted, such as String.  I wanted to create a class which was a
Sortable Array, so I inherited it from both classes.

As the heirarchy is now, Container and Sortable have Object as
non-virtual base classes.  Thus my new class, SortableArray, has two
Objects inside it.  This is a problem, because the generic operators
such as << and == are defined to operate on Objects, but a
SortableArray cannot be implicitly cast to an Object.  Rather than
explicitly cast a SortableArray to an Array or a Sortable every time I
want to use it, I decided to make Object a virtual base class of
Container and Sortable.

This caused another problem, because you cannot cast downwards from a
virtual base class to a derived class.  The main place this caused a
problem was in the virtual function isEqual, which is a pure virtual
function in Object.  (This is what the operator == calls.)  The
function isEqual takes an Object reference as its argument.  Thus my
SortableArray class defines isEqual(Object &O), in which O is cast to
a SortableArray so that it can be compared to *this.  But an Object
reference cannot be cast down to a SortableArray reference.

My kludgey solution to this was to define a pure virtual function in
Object called me.  In every non-abstract class defined from Object, me
returns a void pointer to this, i.e. (void *)this.  Since any pointer can
be cast to a void pointer, this is legal.  Then in SortableArray's
isEqual function, to cast Object &O to a SortableArray reference, I
call O's me function, and cast that:

	SortableArray &S = *(SortableArray *)O.me()

Since a void pointer may be cast to any other pointer, this is legal.
This seems to work, although I have some other bugs which may or may
not have anything to do with this.

My idea to change the language to remedy this problem is to allow
overloaded virtual functions.  This is best described with an example:

	class Base {
		virtual int f(Base &);
	}

	class Derived : public Base {
		virtual int f(Derived &);
	}

	Base B;
	Derived D;

In this example, D.f(B) will call B::f, D.f(D) will call D::f, and
B.f(B) will call B::f, as it is now; but B.f(D) will call D::f, rather
than B::f.

Will this work?  Is this feasible?  Is there some other situation
where you would prefer to have it the way it is now?  Can you suggest
a better way to solve my problem?  Please send me email, and I will
post a summary of the advice given.

Doug Orleans
dougo@soda.berkeley.edu

howells@frieda.mitre.org (Timothy P. Howells) (05/28/91)

Hi,

  dougo@soda.berkeley.edu (Doug Orleans) wrote about the fact that C++
doesn't permit you to cast down from a virtual base class to one of
its derived classes.

  I had the same problem with the prohibition against casting down
from a virtual base class.  I also tried beating the compiler by first
casting to (void *), but I found that this caused errors in accessing
member values.  I suspect your bugs are indeed related to the kludge.

  My "solution" was to reference all my objects via unions with tags
indicating the types.  Of course this works, but it seems to defeat
the purpose of object oriented programming to a large extent.

  Another possible "solution" is to use a virtual fuction to retrieve
the data value, but it seems silly to use a function call (which must
be resolved at run time) when all you really want is a data reference.
This approach also forces you to declare a stub virtual function in
the parent.

  I'm new to C++.  Am I missing something here, or is this a flaw in
the language?  Since the limitation only applies to *virtual* base
classes, I assume that there is some implementation problem regarding
multiple inheritance.  On the other hand, if unions can be made to
work, why not casting downwards?

Doug.Orleans@sunbrk.FidoNet.Org (Doug Orleans) (05/28/91)

Recently I began my first major C++ programming project.  I am using
Turbo C++ 1.0 on an XT clone.  Turbo C++ comes with a fairly extensive
class heirarchy, so I decided to use that as a base.  I ran up against
a major problem, however, when I tried to use multiple inheritance,
and I came up with two solutions: one is a kludge, and the other
involves a change in the language definition.

My problem was this:  there is a class Object which lies at the root
of the heirarchy.  One branch of the tree is the Container branch,
which has classes such as Container, Collection, and Array.  Another
branch is the Sortable branch, which has classes of objects which can
be sorted, such as String.  I wanted to create a class which was a
Sortable Array, so I inherited it from both classes.

As the heirarchy is now, Container and Sortable have Object as
non-virtual base classes.  Thus my new class, SortableArray, has two
Objects inside it.  This is a problem, because the generic operators
such as << and == are defined to operate on Objects, but a
SortableArray cannot be implicitly cast to an Object.  Rather than
explicitly cast a SortableArray to an Array or a Sortable every time I
want to use it, I decided to make Object a virtual base class of
Container and Sortable.

This caused another problem, because you cannot cast downwards from a
virtual base class to a derived class.  The main place this caused a
problem was in the virtual function isEqual, which is a pure virtual
function in Object.  (This is what the operator == calls.)  The
function isEqual takes an Object reference as its argument.  Thus my
SortableArray class defines isEqual(Object &O), in which O is cast to
a SortableArray so that it can be compared to *this.  But an Object
reference cannot be cast down to a SortableArray reference.

My kludgey solution to this was to define a pure virtual function in
Object called me.  In every non-abstract class defined from Object, me
returns a void pointer to this, i.e. (void *)this.  Since any pointer can
be cast to a void pointer, this is legal.  Then in SortableArray's
isEqual function, to cast Object &O to a SortableArray reference, I
call O's me function, and cast that:

	SortableArray &S = *(SortableArray *)O.me()

Since a void pointer may be cast to any other pointer, this is legal.
This seems to work, although I have some other bugs which may or may
not have anything to do with this.

My idea to change the language to remedy this problem is to allow
overloaded virtual functions.  This is best described with an example:

	class Base {
		virtual int f(Base &);
	}

	class Derived : public Base {
		virtual int f(Derived &);
	}

	Base B;
	Derived D;

In this example, D.f(B) will call B::f, D.f(D) will call D::f, and
B.f(B) will call B::f, as it is now; but B.f(D) will call D::f, rather
than B::f.

Will this work?  Is this feasible?  Is there some other situation
where you would prefer to have it the way it is now?  Can you suggest
a better way to solve my problem?  Please send me email, and I will
post a summary of the advice given.

Doug Orleans
dougo@soda.berkeley.edu

 * Origin: Seaeast - Fidonet<->Usenet Gateway - sunbrk (1:343/15.0)

Timothy.P..Howells@sunbrk.FidoNet.Org (Timothy P. Howells) (05/28/91)

Hi,

  dougo@soda.berkeley.edu (Doug Orleans) wrote about the fact that C++
doesn't permit you to cast down from a virtual base class to one of
its derived classes.

  I had the same problem with the prohibition against casting down
from a virtual base class.  I also tried beating the compiler by first
casting to (void *), but I found that this caused errors in accessing
member values.  I suspect your bugs are indeed related to the kludge.

  My "solution" was to reference all my objects via unions with tags
indicating the types.  Of course this works, but it seems to defeat
the purpose of object oriented programming to a large extent.

  Another possible "solution" is to use a virtual fuction to retrieve
the data value, but it seems silly to use a function call (which must
be resolved at run time) when all you really want is a data reference.
This approach also forces you to declare a stub virtual function in
the parent.

  I'm new to C++.  Am I missing something here, or is this a flaw in
the language?  Since the limitation only applies to *virtual* base
classes, I assume that there is some implementation problem regarding
multiple inheritance.  On the other hand, if unions can be made to
work, why not casting downwards?

 * Origin: Seaeast - Fidonet<->Usenet Gateway - sunbrk (1:343/15.0)

chip@tct.com (Chip Salzenberg) (06/01/91)

According to Doug.Orleans@sunbrk.FidoNet.Org (Doug Orleans):
>My problem was this:  there is a class Object which lies at the root
>of the heirarchy.  One branch of the tree is the Container branch,
>which has classes such as Container, Collection, and Array.

This approach, with all (interesting) classes derived from Object,
is sometimes called a "type tree".  In my opinion, a type tree is a
poor match for the C++ language.  I avoid it.

I take care in my C++ programming not to lose the static type of any
object for which I will need that static type later on.  Obviously,
this means that I cannot use just one Array class, one Bag class, etc.

It's better to be type-safe than to be convenient.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.com>, <uunet!pdn!tct!chip>
          perl -e 'sub do { print "extinct!\n"; }   do do()'

pkr@media03.UUCP (Peter Kriens) (06/03/91)

Chip Salzberg writes:
> I take care in my C++ programming not to lose the static type of any
> object for which I will need that static type later on.  Obviously,
> this means that I cannot use just one Array class, one Bag class, etc.
>
> It's better to be type-safe than to be convenient.

Is there any research done validates this statement? As far as I can see
there are pro's and con's for both methods. In the C++ language being
typesafe means code duplication and handling a lot more cases. Resulting
in a program with lots more lines of code and minimising reuse. Type
safe means that you got your types ok, but not all the other 90%
or mistakes you can make. So evenif it compiles, it probably doesnt
run at once. So which one wins,lesser code ( is I assume lesser bugs) and
more reuse, or the type safety?

Peter Kriens

mnm@hpcupt3.cup.hp.com (Michey Mehta) (06/04/91)

If you have NIH sources, you might want to see how they handle castdowns
from a virtual base. There was also an article by Desmond D'Souza on
this topic in the July/August issue of The C++ Report.

Michey Mehta
mnm@hpda.hp.com

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

According to pkr@media03.UUCP (Peter Kriens):
>Chip Salzberg writes:
>> I take care in my C++ programming not to lose the static type of any
>> object for which I will need that static type later on.  Obviously,
>> this means that I cannot use just one Array class, one Bag class, etc.
>>
>> It's better to be type-safe than to be convenient.
>
>Is there any research done validates this statement?

Not as far as I know.  My personal experience and my understanding of
the strengths and weaknesses of C++ led me to that conclusion.

>In the C++ language being typesafe means code duplication and handling
>a lot more cases.

Not really.  The preprocessor is a wonderful thing, and templates are
even better.  I cannot create just one Array class, for example; but
with the preprocessor, I can automatically generate StringArray,
IntArray, etc. without any additional coding.  And templates bring the
effort required to create specialized classes down to almost zero.

>So which one wins,lesser code ( is I assume lesser bugs) and
>more reuse, or the type safety?

Reuse is not much of an issue, thanks to the preprocessor and
templates.  Therefore, for me, the only remaining issue is bugs.  And
I'd prefer to catch typing bugs at compile time instead of letting
them lie in wait to bite me at run time.

"Never put off until run time that which can be done at compile time."
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.com>, <uunet!pdn!tct!chip>
          perl -e 'sub do { print "extinct!\n"; }   do do()'

gary@matterhorn.ctc.contel.com (Gary Bisaga x4219) (06/05/91)

In article <45680006@hpcupt3.cup.hp.com>, mnm@hpcupt3.cup.hp.com (Michey Mehta) writes:
> If you have NIH sources, you might want to see how they handle castdowns
> from a virtual base. There was also an article by Desmond D'Souza on
> this topic in the July/August issue of The C++ Report.
I realize this is probably a FAQ, but where does one get ahold of the NIH sources?
Also, where does one get ahold of a FAQ?  In the time I have been reading this list
I have seen several references to the FAQ but nobody saying where to get it from.

Gary Bisaga (gary@ctc.contel.com)