contravariant argument types: wanna?

Steven Schveighoffer schveiguy at yahoo.com
Tue Sep 22 18:37:00 PDT 2009


On Tue, 22 Sep 2009 21:25:44 -0400, Jeremie Pelletier <jeremiep at gmail.com>  
wrote:

> Steven Schveighoffer wrote:
>> On Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier  
>> <jeremiep at gmail.com> wrote:
>>
>>> Andrei Alexandrescu wrote:
>>>> Hello,
>>>>   Today, overriding functions have covariant return types:
>>>>  class A {
>>>>     A clone();
>>>> }
>>>>  class B : A {
>>>>     B clone(); // fine, overrides A.clone
>>>> }
>>>>  That is entirely principled and cool. Now the entire story is that  
>>>> overriding function may have not only covariant return types, but  
>>>> also contravariant argument types:
>>>>  class A {
>>>>     A fun(B);
>>>> }
>>>>  class B : A {
>>>>     B fun(A); // fine (in theory), overrides A.fun
>>>> }
>>>>  Today D does not support contravariant arguments, but Walter told me  
>>>> once he'd be quite willing to implement them. It is definitely the  
>>>> right thing to do, but Walter would want to see a compelling example  
>>>> before getting to work.
>>>>  Is there interest in contravariant argument types? If so, do you  
>>>> know of a killer example?
>>>>   Thanks,
>>>>  Andrei
>>>
>>> I can't think of an use for contravariant parameters, since a B is  
>>> guaranteed to always be a A, I don't see the point of being able to  
>>> declare fun(A).
>>>
>>> However, I would love to hear about covariant parameters, it would be  
>>> most useful for interface implementations:
>>>
>>> interface A {
>>>     A fun(A);
>>> }
>>> class B : A {
>>>     B fun(B);
>>> }
>>> class C : A {
>>>     C fun(C);
>>> }
>>>
>>> Currently you need some pretty boring boilerplate code, which isn't  
>>> complicated but gets repetitive when you have hundreds of such cases:
>>>
>>> class B : A {
>>>     B fun(A) {
>>>         if(B b = cast(B)b) // do stuff
>>>         else throw Error("Invalid object type");
>>>     }
>>> }
>>  I don't know if this is possible:
>>  A a = new C;
>>  a.fun(new A); // oops, you just passed an A into a function which  
>> requires a C!
>>  Are you suggesting that the compiler insert dynamic cast checks  
>> everywhere?  Cause that seems like a lot of overhead...
>>  -Steve
>
> Not everywhere, only where it detects covariant/contravariant overrides  
> or implementations. In these cases you would already use explicit  
> dynamic casts so the compiler generated code would just lower the  
> required boilerplate.

I don't think it's worth the trouble.  Dynamic casts are not as cheap as  
implicit casts.  Contravariance on parameters can be statically proven by  
the compiler.  I agree Andrei's example isn't that compelling (to be fair,  
he did ask if anyone had a good example, indicating his wasn't), but there  
are other examples that are more compelling (see the bug report I  
referenced in a separate sub-thread).

For instance, if you only ever use class C, and never instantiate an A or  
B instance, you still pay the dynamic cast penalty every time you call  
fun!  It doesn't sound to me like a good design.

I suppose you probably have run into this before, perhaps a real example  
would be more convincing.

-Steve



More information about the Digitalmars-d mailing list