[comp.lang.c++] changing return type of virtual

rpj@redcloud.cad.mcc.com (Rich Johns) (07/28/89)

Assume a situation like the following:

class Window {
public:
 virtual Window* Copy();
 // other stuff
};


class BorderedWindow : public Window {
public:
  BorderedWindow* Copy();
// other stuff
};


class ScrollableWindow : BorderedWindow {
public:
  ScrollableWindow* Copy();
// other stuff
};

I would like to be able to change the return type of the virtual
Copy(). G++ 1.35.1 rejects this, and I assume cfront would do the
same.  I have only recently upgraded to g++ 1.35.1, and was previously
using 1.32 which allowed me to do this. Could someone explain why this
is not allowed. It seems like the compiler could easily determine
whether the change in return type were reasonable or dangerous. That
is to say, if the changed return type is derived from the return type
in the base class, let it be.

I'm trying to avoid, what seems to me to be, unecessary casting. If
BorderedWindow and ScrollableWindow each have methods their base
classes do not, then I lose the ability to deal with all of them
generically. For example, only BorderedWindows have a borderWidth, and
only scrollable windows have a ScrollUp() method. What I could do is
define ScrollUp() as a virtual on Window with a dummy definition. This
is alright I guess, although not really intuitive because a plain
window does not scroll. I could also put boderWidth variable on
Window, but this seems even less intuitive. Nonetheless, I would do it
if not for the fact that object Window is part of a library, which I
can't alter. (It just so happens that in this case I could modifiy the
library, but I don't want to do this. I want to be able to pick up new
versions of the library and not have to worry about always
retrofitting my own personal hacks).

I could also establish a new base clas called MyWindow, derived from
Window. But would'nt I then have to rename Copy() to MyCopy() in order
to change its return type? The only solution seems to be messy casting.

Am I thinking about this in the right way?

Rich Johns, MCC CAD Program | 3500 W. Balcones Center Dr., Austin, TX 78759
ARPA: johns@mcc.com         | Phone: [512] 338-3714
UUCP: {uunet,harvard,gatech,pyramid}!cs.utexas.edu!milano!cadillac!johns

lpringle@bbn.com (Lewis G. Pringle) (07/29/89)

In article <1974@cadillac.CAD.MCC.COM> rpj@redcloud.cad.mcc.com (Rich Johns) writes:
>
>I would like to be able to change the return type of the virtual
>Copy(). G++ 1.35.1 rejects this, and I assume cfront would do the
>same.  I have only recently upgraded to g++ 1.35.1, and was previously
>using 1.32 which allowed me to do this. Could someone explain why this
>is not allowed. It seems like the compiler could easily determine
>whether the change in return type were reasonable or dangerous. That
>is to say, if the changed return type is derived from the return type
>in the base class, let it be.

I agree that this is a misfeature of the C++ language, and there are
related problems which have hounded me.  For much the same reason, I
often want to tell the compiler that a data member (reference or pointer),
is a (reference or pointer) to a subclass of the declared type.

The problem, it that for data members it is ALMOST ALWAYS DANGEROUS, and that
is why (assumtion) the compiler does not allow it.  You see, a base class
pointer could then be used to assign a bad value.

For virtual functions arguments the same danger applies.

However, the specific thing you suggested - return values - does appear
to be safe, and so I too think it should be added to the language.
Any base class calling your method cannot be bothered that the retuned type
was really known to be a (pointer/reference) to a subtype of the type it
expected.  What the feature adds to the language is a type-safe way to help
deal with a very common occurence: parallel subclassing in two hierarchies,
one used by the other.  All in all, I think its a good idea!

BTW, I would be very interested in other peoples ideas about how to deal
with this problem of having an owning class and an owned class, and
subclassing both, and simply getting the right behavior.

Example:
	ScrollBar has an data mamber Thumb.  Now I want to make a
ScrollBarWithSizeableThumb.  I subclass both classes to the right thing,
and add a SetSize() method to thunb (so the ScrollBarWithSIzeableThumb can
reset it).  Now the ScrollBar has an fThumb field of type (Thumb*).  Do
I use this and always cast?  Do I use a new data member fThumb with the new
type and shadow the parent class name?  What is the best way to deal with
this kind of problem????
"OS/2: half an operating system for half a computer."

In Real Life:		Lewis Gordon Pringle Jr.
Electronic Mail:	lpringle@labs-n.bbn.com
Phone:			(617) 873-4433

vaughan@mcc.com (Paul Vaughan) (08/01/89)

Lewis Pringle gives a good example of the issue at hand

>Example:
>	ScrollBar has an data mamber Thumb.  Now I want to make a
>ScrollBarWithSizeableThumb.  I subclass both classes to the right thing,
>and add a SetSize() method to thunb (so the ScrollBarWithSIzeableThumb can
>reset it).  Now the ScrollBar has an fThumb field of type (Thumb*).  Do
>I use this and always cast?  Do I use a new data member fThumb with the new
>type and shadow the parent class name?  What is the best way to deal with
>this kind of problem????

>The problem, it that for data members it is ALMOST ALWAYS DANGEROUS, and that
>is why (assumtion) the compiler does not allow it.  You see, a base class
>pointer could then be used to assign a bad value.

>For virtual functions arguments the same danger applies.

	I've been considering ways that the compiler could verify
correct handling of subclassed data members.  I can't say that I've
thought it out thoroughly, but here goes.  The main idea is that the
data member cannot be publicly accessable.  This insures that only
friends and member functions can deal directly with the data member.
Now, it seems feasible to me that the compiler could simply verify
that friends and members dealt with the data member in a type-safe fashion.

	This implies that any member function that assumes a type for
the data member (that is, assigns a value, copies it via assignment,
returns it as a value, passes it as a paramemter, etc.) must be
virtual, and that the subclass must have it's own local definition for
each such member function (and that the local definitions assign the
right type).  It also implies that a virtual function that assumes a
type for the data member may not be called directly using a parent::
construct.  I'm not sure how this would interact with constructors and
the rules about how virtual functions act within them.  It seems to me
that these are all things that the compiler could verify without
difficulty, thereby proving the program to be type-save without
relying on programmer assertions (casts).

	The same argument applies for virtual function arguments, as
long as the virtual function is protected.  The function cannot be
called, except from other member functions which are virtual and have
a local definition.

	This might also work for subclassed return types, allowing
virtual functions to return subclassed objects or pointers to
subclassed objects.

	Andrew, you mention that there is a problem with type
ambiguity if a member function could return a reference to a
subclassed type.  I don't think this is a problem given the rules
outlined here.  However, if you change your example to returning a
pointer, rather than a reference, I'm not sure that there is a problem
even with less restrictive rules.  For instance, if you make that
change, how does your example differ from this program:


#include <stream.h>

class A {
public:
  int i;
};

class B : public A {
public:
  B() { j = 1;};
  int j;
};

main() {
  B* bp = new B;
  cout << "j = " << bp->j << "\n";	
			// prints j = 1 as expected
  A* ap = bp; 		// loses track of actual type here
  A a;
  a = *ap;		// copies the A part into an A size space
  *ap = a;		// copies the A part into a B size space
  cout << "j = " << bp->j << "\n";  	
			// prints j = 1 (unchanged, as expected)
  delete(ap);		// don't know what this does (memory leak?)
  cout << "works so far\n";
}

	Here the normal language rules specify that only the A part of
*ap gets copied into a.  In the reference case, where a reference to
some potentially subclassed type is being assigned via a reference to
another potentially subclassed type (as in your example), I would
expect the target reference to be treated as the type visible at
compilation time, (that is like the parent type).  The value reference
would naturally be treated like the parent type, just as the pointer
is in the above program.  Reclaiming the extra space and figuring out
what to do with calling destructors is more difficult.  But then, I'm
not sure what happens in the above program when the delete(&a) is
executed either.  I don't understand the argument about overloaded
functions either.  Overloaded functions are already defined to only
work on the type as visible at compile time.  Of course, I see this as
a weakness (but an efficient weakness) as compared to true runtime
multiple dispatch (ala generic functions in CLOS), but that's a
different issue.

	BTW, this issue is important to me.  It comes up a lot when
making libraries and toolkits of things for users to put together.



 Paul Vaughan, MCC CAD Program | ARPA: vaughan@mcc.com | Phone: [512] 338-3639
 Box 200195, Austin, TX 78720  | UUCP: ...!cs.utexas.edu!milano!cadillac!vaughan