covariant final interface functions

Steven Schveighoffer schveiguy at yahoo.com
Fri Mar 19 07:36:32 PDT 2010


On Fri, 19 Mar 2010 09:28:21 -0400, Justin Johansson <no at spam.com> wrote:

> Maybe I don't understand your problem exactly, but in answer to
> what you said here ...
>
>> Because of covariance, this works
>> awesome because you always get back the type you are using.
>
> .. If you know in the first place (presumably by static analysis) the  
> type you
> are using then you know the type you expect to be returned, so why do
> you need to even use an interface and expect some magic covariance to
> be of use to you.

Because people may want to use the interface vs the actual class.  There  
is no point of an interface if people don't use it to abstract the  
implementation.  I want the interface to call the same function as the  
class, but the return type should be covariant.  That is, someone who is  
using the derived type and wants to chain calls on such functions should  
not be forced to use only the interface supported calls after they make  
the first one.

> Like I say, perhaps I misunderstand your point but feel from my own
> endeavours that covariant return types are rarely useful and only serve
> to push more entries into a virtual function table further down an  
> inheritance
> tree for no purposeful gain in the end.

covariant functions occupy one slot of the vtable.  You may be  
misunderstanding how covariance works.  See below.

>
> Perhaps you could elaborate on what the real problem is that you want to  
> solve.
>
> Please accept my apologies if your question/proposal is obvious to all  
> and sundry;
> just that personally I cannot reconcile this in my own mind that there  
> is an end
> to be met here by your means.

No apologies needed :)  I'll explain what I mean:

Covariance works like this:

class C
{
    C doSomething() {...; return this;}
}

class D : C
{
    D doSomething() {...; return this;}
    void doSomethingElse() {...}
}

If I have a D, I can call d.doSomething().doSomethingElse(), whereas if I  
have a C reference to a D, I cannot call the doSomethingElse part, but  
both c.doSomething() and d.doSomething() invoke the exact same function  
(D's version).

If you look at D's virtual table, it only has 2 entries, because the  
covariant version overrides the base version.  In C++ this is not  
possible, because it does not consider that you can return a pointer to  
something that is both a C and a D at the same time.  The benefit is one  
implementation for both interfaces (C or D).

However, if you have simple "wrapper" functions, like the ones I've  
outlined, in order to achieve covariance on those wrappers, you must  
override them.

e.g.:

class C
{
    C doSomething(ref int x) {...; return this;}
    C doSomething() {int dummy; return doSomething(dummy);}
}

class D : C
{
    D doSomething(ref int x) {...; return this;}
    D doSomething() {int dummy; return doSomething(dummy);} // need this  
reimplementation to achieve covariance
}

That second function of D is a complete duplicate of code, and poses a  
maintenance nightmare.  If this is a large hierarchy, any changes I make  
to the base wrapper must be duplicated in each derived class.

Not only that, but all versions of the function are exact copies!  There  
is no difference in the generated code whatsoever.

With the advent of final functions in interfaces, I can achieve wrapper  
functions in an interface (I'm using that now for some things in  
dcollections).  However, I can't make them covariant.  So I'm forced to  
write the wrapper functions in the implementation instead of the  
interface, where it belongs.

It would be nice in both the interface and the class hierarchy cases to  
simply identify that a function is the same, but permanently covariant  
because it returns a pointer to this.  Covariance always is possible if  
you return this.

That's all I'm saying.

-Steve



More information about the Digitalmars-d mailing list