Thread overview
Covariant returns for interfaces
Oct 09, 2004
Sean Kelly
Oct 10, 2004
Ben Hinkle
Oct 11, 2004
Stewart Gordon
Oct 11, 2004
Ben Hinkle
Oct 11, 2004
Stewart Gordon
Oct 11, 2004
Ben Hinkle
Oct 11, 2004
Sean Kelly
Oct 11, 2004
Sean Kelly
Oct 11, 2004
Sean Kelly
October 09, 2004
I don't think this is a bug, but the inconsistency strikes me as a bit odd.  Basically, covariant returns are allowed for class inheritance but not for interface inheritance.  For example:

# import std.stdio;
#
# interface I
# {
#     I getI();
#}
#
# class B
# {
# public:
#     abstract B getB();
# }
#
# class C : I
# {
# public:
#     I getI() { return new C(); } // A
#     C getB() { return new C(); }
# }
#
#
# void main()
# {
#     C c = new C();
#     I i = c.getI();
#     C e = c.getB();
# }

This code works just fine, but if I change line A to:

C getI() { return new C(); }

then I get an error: "class C interface function I.getI isn't implemented."  What I'm not sure of is whether this is how things *should* be.  I want to say yes, but on one level it seems odd that covariant returns aren't allowed for interfaces.


Sean
October 10, 2004
Sean Kelly wrote:

> I don't think this is a bug, but the inconsistency strikes me as a bit odd.  Basically, covariant returns are allowed for class inheritance but not for interface inheritance.  For example:
> 
> # import std.stdio;
> #
> # interface I
> # {
> #     I getI();
> #}
> #
> # class B
> # {
> # public:
> #     abstract B getB();
> # }
> #
> # class C : I
> # {
> # public:
> #     I getI() { return new C(); } // A
> #     C getB() { return new C(); }
> # }
> #
> #
> # void main()
> # {
> #     C c = new C();
> #     I i = c.getI();
> #     C e = c.getB();
> # }
> 
> This code works just fine, but if I change line A to:
> 
> C getI() { return new C(); }
> 
> then I get an error: "class C interface function I.getI isn't implemented."  What I'm not sure of is whether this is how things *should* be.  I want to say yes, but on one level it seems odd that covariant returns aren't allowed for interfaces.
> 
> 
> Sean

I assume it's because the cast from C to I is non-trivial while casting from
a subclass to a superclass is trivial (ie - it doesn't generate any code).
Try the following to see what I mean:
interface A {};
class B:A {};
class C:B {};
int main() {
  B b = new B;
  A a = b;
  printf("%p %p\n",b,a); // not the same pointer
  C c = new C;
  b = c;
  printf("%p %p\n",b,c); // the same pointer
  return 0;
}


October 11, 2004
Ben Hinkle wrote:
> Sean Kelly wrote:
> 
>> I don't think this is a bug, but the inconsistency strikes me as a  bit odd.  Basically, covariant returns are allowed for class inheritance but not for interface inheritance.  For example:
<snip>

I do.

It's been reported already, along with a related problem that probably boils down to the same thing:

http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D.bugs/1726

> I assume it's because the cast from C to I is non-trivial while casting from a subclass to a superclass is trivial (ie - it doesn't generate any code).  Try the following to see what I mean:
<snip>

What would that have to do with anything?

Stewart.
October 11, 2004
Stewart Gordon wrote:

> Ben Hinkle wrote:
>> Sean Kelly wrote:
>> 
>>> I don't think this is a bug, but the inconsistency strikes me as a bit odd.  Basically, covariant returns are allowed for class inheritance but not for interface inheritance.  For example:
> <snip>
> 
> I do.
> 
> It's been reported already, along with a related problem that probably boils down to the same thing:
> 
> http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D.bugs/1726
> 
>> I assume it's because the cast from C to I is non-trivial while casting from a subclass to a superclass is trivial (ie - it doesn't generate any code). Try the following to see what I mean:
> <snip>
> 
> What would that have to do with anything?

It is important when someone calls a function and expects an interface
pointer returned but gets an Object pointer. The caller starts
dereferencing the bogus "interface pointer" and has random behavior. To put
it another way take the OP's example and suppose C.getI could be declared
as returning C. Then if one executed the line
 I ci = cast(I)c;
 I i = ci.getI(); // dynamic dispatch for I.getI
will not return the right pointer. By declaring the return type as I there
is an implicit cast at the end of C.getI to the interface which adjusts the
pointer as my example code indicated. If the return type was C that
adjustment wouldn't happen since there isn't any implicit cast to I. That
would mean the ci.getI() call would think it was getting the interface
pointer but sometimes gets the Object pointer instead.

> 
> Stewart.

October 11, 2004
Ben Hinkle wrote:
> Stewart Gordon wrote:
> 
>> Ben Hinkle wrote:
<snip>
>>> I assume it's because the cast from C to I is non-trivial while casting from a subclass to a superclass is trivial (ie - it doesn't generate any code). Try the following to see what I mean:
>> 
>> <snip>
>> 
>> What would that have to do with anything?
> 
> It is important when someone calls a function and expects an interface pointer returned but gets an Object pointer. The caller starts dereferencing the bogus "interface pointer" and has random behavior.

I think we're talking a bit at cross purposes.

The problem you're thinking of is that a method with an interface return type cannot be covariantly overridden with a class return type.

OTOH, I was thinking of the problem that a method defined in an interface cannot be implemented with a covariant return type.

The OP doesn't clarify which bug is being reported, since the example does both simultaneously.

The OP's example would have equally failed if we'd defined

    interface I {
        B getB();
    }

instead.  OTOH, your interpretation means that

    class B {
        abstract public I getI();
    }

    class C : B {
        public C getI() { ... }
    }

would fail.  I'm not sure if I've tried this myself.

> To put it another way take the OP's example and suppose C.getI could be declared
> as returning C. Then if one executed the line  I ci = cast(I)c;
>  I i = ci.getI(); // dynamic dispatch for I.getI
> will not return the right pointer. By declaring the return type as I there
> is an implicit cast at the end of C.getI to the interface which adjusts the
> pointer as my example code indicated. If the return type was C that
> adjustment wouldn't happen since there isn't any implicit cast to I.
<snip>

You have a point.  I can now guess that an interface pointer is a combination of an object pointer and a pointer to the interface's vtbl.  I wonder if they can be implemented differently such that this problem doesn't exist....

Stewart.
October 11, 2004
"Stewart Gordon" <smjg_1998@yahoo.com> wrote in message news:cke6id$1hkp$1@digitaldaemon.com...
> Ben Hinkle wrote:
> > Stewart Gordon wrote:
> >
> >> Ben Hinkle wrote:
> <snip>
> >>> I assume it's because the cast from C to I is non-trivial while casting from a subclass to a superclass is trivial (ie - it doesn't generate any code). Try the following to see what I mean:
> >>
> >> <snip>
> >>
> >> What would that have to do with anything?
> >
> > It is important when someone calls a function and expects an interface pointer returned but gets an Object pointer. The caller starts dereferencing the bogus "interface pointer" and has random behavior.
>
> I think we're talking a bit at cross purposes.
>
> The problem you're thinking of is that a method with an interface return type cannot be covariantly overridden with a class return type.
>
> OTOH, I was thinking of the problem that a method defined in an interface cannot be implemented with a covariant return type.
>
> The OP doesn't clarify which bug is being reported, since the example does both simultaneously.
>
> The OP's example would have equally failed if we'd defined
>
>      interface I {
>          B getB();
>      }
>
> instead.  OTOH, your interpretation means that
>
>      class B {
>          abstract public I getI();
>      }
>
>      class C : B {
>          public C getI() { ... }
>      }
>
> would fail.  I'm not sure if I've tried this myself.

I see what you mean. It does seem wierd that an interface can't be implemented covariantly. I don't know if there is a technical reason for this.

> > To put it another way take the OP's example and suppose C.getI could be
declared
> > as returning C. Then if one executed the line
> >  I ci = cast(I)c;
> >  I i = ci.getI(); // dynamic dispatch for I.getI
> > will not return the right pointer. By declaring the return type as I
there
> > is an implicit cast at the end of C.getI to the interface which adjusts
the
> > pointer as my example code indicated. If the return type was C that adjustment wouldn't happen since there isn't any implicit cast to I.
> <snip>
>
> You have a point.  I can now guess that an interface pointer is a
> combination of an object pointer and a pointer to the interface's vtbl.
>   I wonder if they can be implemented differently such that this problem
> doesn't exist....

I was thinking about that, too. I'm not a compiler writer so I don't know exactly how interfaces are implemented but there are probably a few possibilities. It would be nice to have objects and interfaces share the same pointer. It's probably not easy, though.

>
> Stewart.


October 11, 2004
In article <cke6id$1hkp$1@digitaldaemon.com>, Stewart Gordon says...
>
>The problem you're thinking of is that a method with an interface return type cannot be covariantly overridden with a class return type.
>
>OTOH, I was thinking of the problem that a method defined in an interface cannot be implemented with a covariant return type.
>
>The OP doesn't clarify which bug is being reported, since the example does both simultaneously.

I was reporting the latter problem.  Sorry for the confusion.


Sean


October 11, 2004
In article <cke6id$1hkp$1@digitaldaemon.com>, Stewart Gordon says...
>
>You have a point.  I can now guess that an interface pointer is a combination of an object pointer and a pointer to the interface's vtbl.
>  I wonder if they can be implemented differently such that this problem
>doesn't exist....

Yup.  I'd forgotten about this.  If that's the way it has to be then that's fine, but it would be nice if it didn't...


Sean


October 11, 2004
In article <ckea98$1l8o$1@digitaldaemon.com>, Sean Kelly says...
>
>In article <cke6id$1hkp$1@digitaldaemon.com>, Stewart Gordon says...
>>
>>The problem you're thinking of is that a method with an interface return type cannot be covariantly overridden with a class return type.
>>
>>OTOH, I was thinking of the problem that a method defined in an interface cannot be implemented with a covariant return type.
>>
>>The OP doesn't clarify which bug is being reported, since the example does both simultaneously.
>
>I was reporting the latter problem.  Sorry for the confusion.

BTW, here's a test case that avoids the other issue:

# class B
# {
# public:
#     abstract B getB();
# }
#
# class C : B
# {
# public:
#     override C getB() { return new C(); }
# }
#
# interface I
# {
#     B getI();
# }
#
#
# class D : I
# {
# public:
#     C getI() { return new C(); }
# }
#
#
# void main()
# {
#
# }

Error is: "test.d(20): class D interface function I.getI isn't implemented"

Now in a sense I can understand the logic behind this--interfaces are kind of like a contract imposed on an implementation.  But I still find it a bit odd that covariant returns works for inheritance from abstract base classes but not from interfaces.  Not a big issue, but I figured it was worth mentioning.


Sean