www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - Covariant returns for interfaces

reply Sean Kelly <sean f4.ca> writes:
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
Oct 09 2004
parent reply Ben Hinkle <bhinkle4 juno.com> writes:
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; }
Oct 09 2004
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
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:


I do. It's been reported already, along with a related problem that probably boils down to the same thing: 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:

What would that have to do with anything? Stewart.
Oct 11 2004
parent reply Ben Hinkle <bhinkle4 juno.com> writes:
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:


I do. It's been reported already, along with a related problem that probably boils down to the same thing: 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:

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.

Oct 11 2004
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Ben Hinkle wrote:
 Stewart Gordon wrote:
 
 Ben Hinkle wrote:


 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.

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.
Oct 11 2004
next sibling parent "Ben Hinkle" <bhinkle mathworks.com> writes:
"Stewart Gordon" <smjg_1998 yahoo.com> wrote in message
news:cke6id$1hkp$1 digitaldaemon.com...
 Ben Hinkle wrote:
 Stewart Gordon wrote:

 Ben Hinkle wrote:


 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


 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


 is an implicit cast at the end of C.getI to the interface which adjusts


 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.

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.

Oct 11 2004
prev sibling next sibling parent reply Sean Kelly <sean f4.ca> writes:
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
Oct 11 2004
parent Sean Kelly <sean f4.ca> writes:
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
Oct 11 2004
prev sibling parent Sean Kelly <sean f4.ca> writes:
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
Oct 11 2004