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

schweitz@lexvmc.vnet.ibm.com ("Eric Schweitz") (04/09/91)

 Subject: Re: typesafe downward casting
 References: <28007BC8.D71@tct.com> <1991Apr2.161809.16952@kodak.kodak.com>
     <27FA1EC4.511C@tct.com> <1991Apr5.150148.21840@kodak.kodak.com>

 According to cok@islsun.Kodak.COM (David Cok):
 >>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 ...

 And 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.

 Possibly (?), another variation on what Chip saying is to add a ``helper''
 class to the hierarchy, such as...


 class Base { /* Can't touch this! */ };
 class Helper : public Base { /* Add virtual function hooks */ };
 class Derived1 : public Helper { /* la la */ };
 class Derived2 : public Helper { /* da da */ };


 This would allow someone to use Helper* (or Helper&?) to access objects of
 classes derived from Base.

 I'm not sure I agree with the idea that contravariance on the
 function's return type would make code easier to code, debug, etc.

 I think that if contravariance were allowed, it would make certain things
 harder to read.  I seems desirable to be able to take an expression one token
 at a time to analyze it.

 Here's what I mean by "harder to read."  Let's assume that the function
 sin() is contravariant...


 int sin (int);
 float sin (float);
 complex sin (complex);

 // implementation of sin's...

 float f, g;
 int i, j;

 i = sin (j);      // OK. uses int sin(int)
 f = sin (g);      // OK. uses float sin(float)
 i = 1 / sin (g);  // Which one here?
                   // (let the compiler decide?)
                   // (What if these are user-defined classes?
                   // How would these be promoted/demoted?)
 i = f / sin (g);  // or here?
 f = sin (i) / g;  // or here?

 complex c;

 c = sin (i) * sin (f) / sin (j) + sin (g); // what does this mean?


 Someone may find this easy to read, but I sure don't. :-)  It just seems
 to ``feel nice'' to know what return-type a function has. Comments?



 Eric

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

In article <9104091317.AA16916@ucbvax.Berkeley.EDU> schweitz@lexvmc.vnet.ibm.com ("Eric Schweitz") writes:
>
	.....
>
> I'm not sure I agree with the idea that contravariance on the
> function's return type would make code easier to code, debug, etc.
>
> I think that if contravariance were allowed, it would make certain things
> harder to read.  I seems desirable to be able to take an expression one token
> at a time to analyze it.
>
> Here's what I mean by "harder to read."  Let's assume that the function
> sin() is contravariant...
>
>
> int sin (int);
> float sin (float);
> complex sin (complex);
>
> // implementation of sin's...
>
> float f, g;
> int i, j;
>

The case you give is complicated by the automatic conversions among
numeric types.  It isn't even proposed by me as something allowed under
"contravariance on function return type", which sticks with derived classes 
returning X*, base classes returning Y*, and X being inherited from Y.
The case you have, assuming everything is user defined rather than numeric
types, is simple overloading, already present in C++, and succumbs to the
rules for overload resolution -- the context of the function call is
ignored, only the parameter types are used:
> i = sin (j);      // OK. uses int sin(int)
> f = sin (g);      // OK. uses float sin(float)
> i = 1 / sin (g);  // Which one here?
>                   // (let the compiler decide?)
>                   // (What if these are user-defined classes?
>                   // How would these be promoted/demoted?)
	    g is a float, so use  float sin(float)
> i = f / sin (g);  // or here?
	    same thing here
	   
> f = sin (i) / g;  // or here?
	    i is an int so use int sin(int)
>
> complex c;
>
> c = sin (i) * sin (f) / sin (j) + sin (g); // what does this mean?
	    Straightforward overloading resolution would resolve this too.
>
>

What I mean by contravariance is this:

class Base {
	...
	virtual Base* clone();
};

class Derived {
	...
	virtual Derived* clone();
//              ^^^^^^^^  C++ currently requires this to be a Base*
};

I have a previous post on implementation which I'll send by e-mail to
any interested person.

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

gjditchfield@watmsg.waterloo.edu (Glen Ditchfield) (04/10/91)

In article <9104091317.AA16916@ucbvax.Berkeley.EDU> schweitz@lexvmc.vnet.ibm.com ("Eric Schweitz") writes:
> ...  Let's assume that the function sin() is contravariant...
>
> int sin (int);
> float sin (float);
> complex sin (complex);

That is not contravariance.  In contravariance, the types of arguments and
the return type change in the opposite way (hence the prefix "contra").

struct adam { ... };
struct bob: public adam {
    virtual bob* contra_f(bob*);
    virtual bob* co_f(bob*);
};

struct chuck: public bob {
    chuck* contra_f(adam*);         // contravariant redefinition
    chuck* co_f(chuck*);            // covariant redefinition
};

schweitz@lexvmc.vnet.ibm.com ("Eric Schweitz") (04/11/91)

cok@islsun.Kodak.COM (David Cok) writes:
>
>The case you give is complicated by the automatic conversions among
>numeric types.  It isn't even proposed by me as something allowed under
>"contravariance on function return type", which sticks with derived classes
>returning X*, base classes returning Y*, and X being inherited from Y.
>The case you have, assuming everything is user defined rather than numeric
>types, is simple overloading, already present in C++, and succumbs to the
>rules for overload resolution -- the context of the function call is
>ignored, only the parameter types are used:
>
>What I mean by contravariance is this:
>
>class Base {
>        ...
>        virtual Base* clone();
>};
>
>class Derived {
>        ...
>        virtual Derived* clone();
>//              ^^^^^^^^  C++ currently requires this to be a Base*
>};
>
>I have a previous post on implementation which I'll send by e-mail to
>any interested person.
>
>David R. Cok
>Eastman Kodak Company -- Imaging Science Lab
>cok@Kodak.COM

Sigh. Sorry for my poor example. It seems to have clouded rather than
clarified my point.  I should have wrote:

   int sin (number);
   float sin (number);
   complex sin (number);

or even better just used something generic:

   base func (base);
   derived func (base);
   morederived func (base);

I apologize for my poor example (which, as pointed out wasn't "contravariant").
(Gosh, I hate when my fingers think for themselves!)

Please note however, that I was NOT referring to "automatic conversion of
numeric types."  "Automatic conversion" is and can be understood and wasn't
the real issue.  I just thought that someone was suggesting ``Surprise'' con-
versions which could be harder(?) to understand.

After looking at your implementation again, it seems that you did in fact
disallow what I was suggesting by proposing that only virtual functions
returning type "base*" or "base&" (in the base class) be contravariant.
(Does this impose the restriction
that sizeof(derived*) == sizeof(base*)?  Note: This isn't an attack,
I'm just curious. :-))

So I guess my original fear was without grounds (maybe?).

Thanks,
Eric

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

In article <9104111419.AA08217@ucbvax.Berkeley.EDU> schweitz@lexvmc.vnet.ibm.com ("Eric Schweitz") writes:
....

>Sigh. Sorry for my poor example. It seems to have clouded rather than
>clarified my point.  I should have wrote:
>
>   int sin (number);
>   float sin (number);
>   complex sin (number);
>
>or even better just used something generic:
>
>   base func (base);
>   derived func (base);
>   morederived func (base);
>
	.....
>(Does this impose the restriction
>that sizeof(derived*) == sizeof(base*)?  Note: This isn't an attack,
>I'm just curious. :-))

The above would not be allowed either.  a) There is not enough info to
disambiguate; b) the return types are not pointers or references.  What
would be allowed is this:
	base* base::func();
	derived* derived::func();
	morederived* morederived::();

There is no need for the restriction on sizes.

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