www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Inheritance and arrays

reply Arafel <er.krali gmail.com> writes:
Hi!

I am a D user coming from java, rather than from C/C++ (although 
obviously also have some exposure to them), and thus apparently one of 
the few people here who likes OO (within reason, of course).

So while I appreciate the fact that D closely follows java's design, I 
wonder why there is no implicit inheritance for arrays (also the same 
applies to AAs):

```d
interface I {}
class C : I {}

void main() {
     I i;
     C c = null;
     i = c; // Works

     I[] ii;
     C[] cc = null;
     // ii = cc; // Doesn't work: Error: cannot implicitly convert 
expression `cc` of type `C[]` to `I[]`
     ii = cast (I[]) cc; // Works, but why do I need to cast?
}
```

The equivalent java code compiles without issue:

```java
interface I {}
class C implements I {}

public class MyClass {
     public static void main(String args[]) {
         I i;
         C c = null;
         i = c; // Works

         I[] ii;
         C[] cc = null;
         ii = cc; // Also works
     }
}
```

Is this a conscious design decision (if so, why?), or just a leak of 
some implementation detail, but that could eventually be made to work?
Jul 03 2023
next sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:
 Hi!

 I am a D user coming from java, rather than from C/C++ 
 (although obviously also have some exposure to them), and thus 
 apparently one of the few people here who likes OO (within 
 reason, of course).

 So while I appreciate the fact that D closely follows java's 
 design, I wonder why there is no implicit inheritance for 
 arrays (also the same applies to AAs):

 ```d
 interface I {}
 class C : I {}

 void main() {
     I i;
     C c = null;
     i = c; // Works

     I[] ii;
     C[] cc = null;
     // ii = cc; // Doesn't work: Error: cannot implicitly 
 convert expression `cc` of type `C[]` to `I[]`
     ii = cast (I[]) cc; // Works, but why do I need to cast?
 }
 ```
The `cast` version "works", but will crash at runtime. In D, as opposed to Java, a reference to an object has a *different pointer value* than a reference to the interface-typed version of that object. This is necessary for efficient compiled virtual method calls on the interface. But for the same reason, you cannot reinterpret an array of objects to an array of interfaces; even if you can implicitly convert each object to that interface, there's a difference between automatically rewriting a value and automatically rewriting every element of an array: one is O(1), the other is O(n) and incurs a GC allocation.
Jul 03 2023
parent reply Arafel <er.krali gmail.com> writes:
That's very clearly an implementation detail leaking: the semantics of 
the language shouldn't depend on how interfaces and classes are implemented.

So then I need to do something like:

```d
ii = cc.map!(a => cast (I) a).array;
```

(I just tested it and it works)

Any reason why it can't be done internally by the language?

I find it most unexpected and confusing and, assuming it won't change 
anytime soon, I think it should at the very least be **clearly** 
documented. And the cast forbidden, since it can't possible work between 
classes and interfaces.

Honestly, this severely limits the usefulness of interfaces, and for 
hierarchies of classes it might be much better to switch to abstract 
classes.

BTW, even for (abstract) classes you need a cast:

```d
import std;

abstract class I {
     abstract void foo();
}
class C : I {
     this(int i) {
         this.i = i;
     }
     override void foo() {
         writeln("In foo: ",i);
     }
     int i;
}

void main() {
     I i;
     C c = new C(1);
     i = c; // Works

     I[] ii;
     C[] cc;
     cc ~= c;
     i.foo();
     // ii = cc; // Doesn't work: Error: cannot implicitly convert 
expression `cc` of type `C[]` to `I[]`
     ii = cast (I[]) cc; // Compiles, apparently works
     //ii = cc.map!(a => cast(I) a).array;
     ii[0].foo();
}
```

Shouldn't `ii = cc` work in this case?

On 3/7/23 12:06, FeepingCreature wrote:
 On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:
 Hi!

 I am a D user coming from java, rather than from C/C++ (although 
 obviously also have some exposure to them), and thus apparently one of 
 the few people here who likes OO (within reason, of course).

 So while I appreciate the fact that D closely follows java's design, I 
 wonder why there is no implicit inheritance for arrays (also the same 
 applies to AAs):

 ```d
 interface I {}
 class C : I {}

 void main() {
     I i;
     C c = null;
     i = c; // Works

     I[] ii;
     C[] cc = null;
     // ii = cc; // Doesn't work: Error: cannot implicitly convert 
 expression `cc` of type `C[]` to `I[]`
     ii = cast (I[]) cc; // Works, but why do I need to cast?
 }
 ```
The `cast` version "works", but will crash at runtime. In D, as opposed to Java, a reference to an object has a *different pointer value* than a reference to the interface-typed version of that object. This is necessary for efficient compiled virtual method calls on the interface. But for the same reason, you cannot reinterpret an array of objects to an array of interfaces; even if you can implicitly convert each object to that interface, there's a difference between automatically rewriting a value and automatically rewriting every element of an array: one is O(1), the other is O(n) and incurs a GC allocation.
Jul 03 2023
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/3/23 7:37 AM, Arafel wrote:
 That's very clearly an implementation detail leaking: the semantics of 
 the language shouldn't depend on how interfaces and classes are 
 implemented.
It's a semantic detail -- casting an array does *not* make a copy, and an array is not a complex object. It's just a block of data. And so it only succeeds if the binary representation can properly be reinterpreted. This is the same for all array types, not just class/interfaces: ```d int i = 1; long x = i; // ok int[] a = [1]; long[] b = a; // error ``` If Java works, it means that Java either handles the conversion by making a copy, or by properly converting on element fetch/store based on type introspection. It also might use a different mechanism to point at interfaces. You might want it to be similar, but there are tradeoffs. With D, though, there is always the possibility of getting what you want by adding a specialized type. Indeed, if you are just going to *read* the definition, a map would work: ```d auto ii = cc.map!(function I(C c) => c); I i = ii[0]; // ok ``` However, if you plan to write the definition as well, you need a more specialized type. It's all doable, though.
 I find it most unexpected and confusing and, assuming it won't change 
 anytime soon, I think it should at the very least be **clearly** 
 documented. And the cast forbidden, since it can't possible work between 
 classes and interfaces.
It is documented: https://dlang.org/spec/arrays.html#implicit-conversions However, the interface conversion is not covered, and it's a bit nebulous as to what is specifically covered with classes/interface. I think it could be improved.
 Honestly, this severely limits the usefulness of interfaces, and for 
 hierarchies of classes it might be much better to switch to abstract 
 classes.
 
 BTW, even for (abstract) classes you need a cast:
...
 
 Shouldn't `ii = cc` work in this case?
I think this has been discussed in another part of the thread. But I did want to point out that an implicit cast to `const` base type array is possible (not for interfaces, since the binary representation is different). -Steve
Jul 03 2023
parent Arafel <er.krali gmail.com> writes:
On 3/7/23 17:41, Steven Schveighoffer wrote:

 
 If Java works, it means that Java either handles the conversion by 
 making a copy, or by properly converting on element fetch/store based on 
 type introspection. It also might use a different mechanism to point at 
 interfaces.
 
As I mentioned in another reply, Java does it by adding a runtime check to the type at insertion. This is a performance hit, and I'm glad it's not done in D. Also, I think I read in some other thread here that Java uses a single pointer for both objects and interfaces, but I think the biggest issue here is the covariance.
 I find it most unexpected and confusing and, assuming it won't change 
 anytime soon, I think it should at the very least be **clearly** 
 documented. And the cast forbidden, since it can't possible work 
 between classes and interfaces.
I want to answer to myself here: it was my knowledge of type theory that was lacking: TIL that even if two types are covariant, arrays of them needn't be, and usually aren't. So I was spoiled by Java ;-) [In case somebody else is interested](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(compu er_science)#Arrays) (also just google it, there are tons of links on the subject). On a final note, thanks to everybody who answered! For all its quirks, D is a great language that I enjoy using, and the community here is great, even if at times it could be a bit more optimistic :-)
Jul 03 2023
prev sibling next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:
 Hi!

 I am a D user coming from java, rather than from C/C++ 
 (although obviously also have some exposure to them), and thus 
 apparently one of the few people here who likes OO (within 
 reason, of course).

 So while I appreciate the fact that D closely follows java's 
 design, I wonder why there is no implicit inheritance for 
 arrays (also the same applies to AAs):

 ...
 
 Is this a conscious design decision (if so, why?), or just a 
 leak of some implementation detail, but that could eventually 
 be made to work?
See also this thread on implementation details: https://forum.dlang.org/post/bibtjiiwpiqzzfwgxgdd forum.dlang.org
Jul 03 2023
prev sibling parent reply Rene Zwanenburg <renezwanenburg gmail.com> writes:
On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:
 Is this a conscious design decision (if so, why?), or just a 
 leak of some implementation detail, but that could eventually 
 be made to work?
Besides the pointer adjustment problem mentioned by FeepingCreature, it's an unsound conversion even with just class inheritance. Consider: ``` class A {} class B : A {} class C : A {} void main() { auto bArr = [new B()]; A[] aArr = bArr; // If this was allowed.. aArr[0] = new C(); // This would be a problem, because bArray would now contain a C. } ```
Jul 03 2023
parent Arafel <er.krali gmail.com> writes:
On 3/7/23 13:03, Rene Zwanenburg wrote:
 On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:
 Is this a conscious design decision (if so, why?), or just a leak of 
 some implementation detail, but that could eventually be made to work?
Besides the pointer adjustment problem mentioned by FeepingCreature, it's an unsound conversion even with just class inheritance. Consider: ``` class A {} class B : A {} class C : A {} void main() {   auto bArr = [new B()];   A[] aArr = bArr; // If this was allowed..   aArr[0] = new C(); // This would be a problem, because bArray would now contain a C. } ```
This is a really good point. I just checked out of curiosity what Java does (because it's allowed there). TIL it throws [an exception](https://docs.oracle.com/javase/8/docs/api/java/lang/Array toreException.html) at runtime, which I guess is not a viable strategy for D. Although when using interfaces, if I cast the individual class instances to interfaces, it should work, right? Because then I'm storing the pointers to the interfaces, not to the actual class, so the arrays are actually different, and not two slices of the same array: ``` import std; interface I { } class C : I { } void main() { C c = new C; I i = c; assert (c is i); // OK, took me a while to notice that "is" is smart enough. assert (cast (void *) c != cast (void *) i); // This is what we really care about. C[] cc = [ c ]; I[] ii; ii = cc.map!( a => cast (I)a ).array; assert (ii[0] is c); // This can be unexpected, even if technically right! assert (cast (void*) ii[0] != cast (void*) c); // Now this is what we need. } ```
Jul 03 2023