www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - object - interface compatibility

reply "Frank Benoit (keinfarbton)" <benoit tionex.removethispart.de> writes:
Today I had a hard lesson to learn. An iface and an object is not
exchangeable. The reference to an object is manipulated if it is casted
to an interface type.

This happens implicit if an object is passed to a var/meth which wants
it to be type of iface.

But this does not happen implicit if you want to pass and array of
objects. This is, because it would mean to iterate over the array, cast
piece-wise and build a new array.
But the consequence is, that the user of the class has to distinguish
between class types and iface types.

I wonder if this should really be in that way?
Is this documented?
Nov 14 2006
parent reply BCS <BCS pathlink.com> writes:
Frank Benoit (keinfarbton) wrote:
 Today I had a hard lesson to learn. An iface and an object is not
 exchangeable. The reference to an object is manipulated if it is casted
 to an interface type.
 
 This happens implicit if an object is passed to a var/meth which wants
 it to be type of iface.
 
 But this does not happen implicit if you want to pass and array of
 objects. This is, because it would mean to iterate over the array, cast
 piece-wise and build a new array.
 But the consequence is, that the user of the class has to distinguish
 between class types and iface types.
 
 I wonder if this should really be in that way?
 Is this documented?
The convention from an object to an interface is unavoidable (at some point). Making this conversion implicit would help but, could get costly. interface I{} class C : I{} foo(I[] arr){} fig(C[] arr){} { foo(cast(I[])arr); } void main() { C[] blah = new C[1000000]; fig(blah); // Ouch O(n) cost in a cast } you can neaten up code a bit with something like this: If[] acast(If, Cl)(Cl[] arr) { If[] ret = new If[arr.length]; foreach(i,e;arr) ret[i] = cast(If)arr[i]; return ret; }
Nov 14 2006
parent reply "Frank Benoit (keinfarbton)" <benoit tionex.removethispart.de> writes:
Yes, thanks for you example.

To make my question more clear:

1.) Is this reference manipulation really absolutely necessary? Why?
2.) Isn't there a way to make class and iface really compatible?
3.) Please document this very cleanly in the D spec.

Frank
Nov 14 2006
next sibling parent reply BCS <BCS pathlink.com> writes:
Frank Benoit (keinfarbton) wrote:
 Yes, thanks for you example.
 
 To make my question more clear:
 
 1.) Is this reference manipulation really absolutely necessary? Why?
 2.) Isn't there a way to make class and iface really compatible?
 3.) Please document this very cleanly in the D spec.

 Frank
In short, No. The long version is that while it could be done, it would add a lot of overhead to everything and only fix a problem that is run into occasionally. Actually I ran into this difference a while back, what it amounts to (if I understand it correctly) is that the calling convention for interfaces is different than that for objects. For object it amounts to some variation on this (in sudo ASM) //obj.test(a, b, c); push c push b push a // push args mov obj -> r1 // load pointer to object mov *r1 -> r2 // load v-tbl push r1 // push "this" call r2[test] // use v-tbl to call test for an interface this is about what is done //inf.test(a, b, c); push c push b push a // push args mov obj -> r1 // load pointer to object mov *r1 -> r2 // load v-tbl // adjust interface to point to object // uses information from interface v-tbl add r1 r2[__offset] -> r1 push r1 // push "this" call *(r2+test) // use v-tbl to call test the interface calling convention adds one more memory fetch and an add. Not a lot but it does add to the overhead and complexity that D's single inheritance model is trying to avoid. Actually I would like to see interface calling convention changed to use a fat pointer for interfaces. This would use a arbitrary context pointer welded to a pointer to a v-tbl. All sorts of cool things could be done with this. interface literals working like delegate literals 3rd party interface implementation for classes* interfaces from structs, arrays and any arbitrary pointers. In effect any reference can be used as a context and as the basis for an interface <D version >= 2.0 feature suggestion> (*) something like this // third party closed source code class Foo { int baz(); char bar(); void go(int i); } // your code interface I { void Go(); void Stop(); } weld Baz : [Foo:I] { void Go() { this.go(1); } void Stop() { this.go(0); } } void func(I); void main() { auto f = new Foo; func(f); // fails (Foo cant be converted to I) func(Baz(f)); // works } Baz is of type I, when a Foo is converted to a Baz all that needs to happen is join the Foo pointer and Baz's v-tbl pointer.
Nov 14 2006
parent reply Pragma <ericanderton yahoo.removeme.com> writes:
BCS wrote:
 Actually I would like to see interface calling convention changed to use 
 a fat pointer for interfaces. This would use a arbitrary context pointer 
 welded to a pointer to a v-tbl. All sorts of cool things could be done 
 with this.
 
 interface literals working like delegate literals
 3rd party interface implementation for classes*
 interfaces from structs, arrays and any arbitrary pointers.
 
 In effect any reference can be used as a context and as the basis for an 
 interface
So then the interface method call turns into: //inf.test(a, b, c); push c push b push a // push args mov obj -> r1 // load pointer to object mov vtbl -> r2 // load v-tbl (obj+vtbl = fat-pointer) push r1 // push "this" call r2[test] // use v-tbl to call test So, this basically sacrifices stack space in favor of a few less opcodes per interface-method call. :) But doesn't this make cast() operations *longer* by one additional move operation? Are method calls really more numerous than casts? On the up-side, it would only make the ABI more consistent, since delegates are pretty much doing the same thing when compared to their function-pointer counterpart (as you already mentioned). A higher degree of consistency there, could buy us a higher degree of consistency with D's constructs, cleaner code, and possibly more-bug-free compiler implementations. -- - EricAnderton at yahoo
Nov 14 2006
parent BCS <BCS pathilink.com> writes:
Pragma wrote:
 BCS wrote:
 Actually I would like to see interface calling convention changed to 
 use a fat pointer for interfaces. This would use a arbitrary context 
 pointer welded to a pointer to a v-tbl. All sorts of cool things could 
 be done with this.
[...]
 
 So then the interface method call turns into:
 
 //inf.test(a, b, c);
 push c
 push b
 push a    // push args
 
 mov obj -> r1    // load pointer to object
 mov vtbl -> r2    // load v-tbl (obj+vtbl = fat-pointer)
 push r1        // push "this"
 call r2[test]    // use v-tbl to call test
 
More or less.
 So, this basically sacrifices stack space in favor of a few less opcodes 
 per interface-method call. :)
 
 But doesn't this make cast() operations *longer* by one additional move 
 operation?  Are method calls really more numerous than casts?
 
That is a problem, it doubles the size of an interface reference. I can see reasons why that would be bad. (BTW this doesn't attempt to solve the object-interface incompatibility.) OTOH It may make objects smaller by one word for each interface they implement. I think that the v-tbl pointer for interfaces is part of the object, with the proposed system, it would be part of the objects v-tbl. It all depends on what your looking for, this system is a bit bigger in one place, a bit smaller in another and provides a few new options for features.
 On the up-side, it would only make the ABI more consistent, since 
 delegates are pretty much doing the same thing when compared to their 
 function-pointer counterpart (as you already mentioned).  A higher 
 degree of consistency there, could buy us a higher degree of consistency 
 with D's constructs, cleaner code, and possibly more-bug-free compiler 
 implementations.
 
Nov 14 2006
prev sibling parent Walter Bright <newshound digitalmars.com> writes:
Frank Benoit (keinfarbton) wrote:
 1.) Is this reference manipulation really absolutely necessary? Why?
There are two ways to implement interfaces. One is where the determination of what functions to call is done by repeated testing of an object to see what interfaces it supports. This is slow. The other way is to do it like C++ does multiple inheritance. Interfaces are really "multiple inheritance lite", and D does it the C++ way, which is very efficient at runtime.
 2.) Isn't there a way to make class and iface really compatible?
Consider a function that takes an interface as an argument. Two different objects, A and B, can implement that interface. How can the function tell that it can call A.foo() when it gets an interface derived from A, when B doesn't have a .foo()?
 3.) Please document this very cleanly in the D spec.
Nov 14 2006