www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - reimplementing an interface in a derived class

reply kdevel <kdevel vogtner.de> writes:
https://dlang.org/spec/interface.html #11 has this code example:

```
interface D
{
     int foo();
}

class A : D
{
     int foo() { return 1; }
}

class B : A, D
{
     override int foo() { return 2; }
}

...

B b = new B();
b.foo();            // returns 2
D d = cast(D) b;
d.foo();            // returns 2
A a = cast(A) b;
D d2 = cast(D) a;
d2.foo();           // returns 2, even though it is A's D, not 
B's D
```

What is the meaning of the ", D"? It does not seem to make a 
difference if it is omitted.
Jan 03
next sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
On Thu, 03 Jan 2019 22:30:48 +0000, kdevel wrote:
 class A : D {
      int foo() { return 1; }
 }
 
 class B : A, D {
 [...]

 What is the meaning of the ", D"? It does not seem to make a difference
 if it is omitted.
B must provide its own implementation of D. It can't simply use A's implementation.
Jan 03
next sibling parent reply Alex <sascha.orlov gmail.com> writes:
On Thursday, 3 January 2019 at 23:23:12 UTC, Neia Neutuladh wrote:
 On Thu, 03 Jan 2019 22:30:48 +0000, kdevel wrote:
 class A : D {
      int foo() { return 1; }
 }
 
 class B : A, D {
 [...]

 What is the meaning of the ", D"? It does not seem to make a 
 difference if it is omitted.
B must provide its own implementation of D. It can't simply use A's implementation.
I assume that is another bug and has nothing to do with interfaces... ´´´ import std.traits; alias parentOfB = BaseClassesTuple!B[0]; void main() { static assert(is(typeof(cast(parentOfB)(new B)) == parentOfB)); assert((cast(parentOfB)(new B)).foo == (new parentOfB).foo); } class A { int foo() { return 1; } } class B : A { override int foo() { return 2; } } ´´´
Jan 03
parent reply Neia Neutuladh <neia ikeran.org> writes:
On Thu, 03 Jan 2019 23:44:15 +0000, Alex wrote:
 I assume that is another bug and has nothing to do with interfaces...
B.foo is both overriding A.foo and implementing D.foo, so that's not a bug.
Jan 03
parent reply Alex <sascha.orlov gmail.com> writes:
On Friday, 4 January 2019 at 00:15:28 UTC, Neia Neutuladh wrote:
 On Thu, 03 Jan 2019 23:44:15 +0000, Alex wrote:
 I assume that is another bug and has nothing to do with 
 interfaces...
B.foo is both overriding A.foo and implementing D.foo, so that's not a bug.
I don't have any interfaces in my example. B.foo overrides A.foo. By casting a B object to be an A object, A's behavior should be granted, shouldn't it?
Jan 03
next sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
On Fri, 04 Jan 2019 00:19:05 +0000, Alex wrote:
 B.foo overrides A.foo. By casting a B object to be an A object, A's
 behavior should be granted, shouldn't it?
I can't think of a single class system that works like that. C++, Java, C#, Dart, and TypeScript all work like D here. GObject in C works like D. The point of OOP is that a bundle of data has particular ways of dealing with it. B has different data (at least theoretically), and that data has different ways of working with it. So if casting to a base class changed something to use the base class's behavior, you'd get bugs almost anywhere you used inheritance, since the derived class's data isn't being modified properly. The only situation in which you might possibly want that sort of behavior is if inheritance were only for ease of implementation, but then you'd want to disallow that sort of cast anyway. It would be like trying to cast an object to one of its fields.
Jan 03
parent reply Alex <sascha.orlov gmail.com> writes:
On Friday, 4 January 2019 at 02:13:27 UTC, Neia Neutuladh wrote:
 I can't think of a single class system that works like that. 
 C++, Java, C#, Dart, and TypeScript all work like D here. 
 GObject in C works like D.
In the example below, the "2" of B.foo is printed only once. Independently of the cast type, after degrading a B to an A the degraded object behaves like an A. ´´´ C++ ´´´ #include <iostream> class A { public: int foo(){return 1;} }; class B : public A { public: int foo(){return 2;} }; void performFoo(A *a) { std::cout << a->foo() << std::endl; } int main() { auto a = new A(); auto b = new B(); std::cout << a->foo() << std::endl; std::cout << b->foo() << std::endl; std::cout << dynamic_cast<A*>(b)->foo() << std::endl; std::cout << static_cast<A*>(b)->foo() << std::endl; std::cout << reinterpret_cast<A*>(b)->foo() << std::endl; performFoo(a); performFoo(b); return 0; } ´´´
 The point of OOP is that a bundle of data has particular ways 
 of dealing with it. B has different data (at least 
 theoretically), and that data has different ways of working 
 with it. So if casting to a base class changed something to use 
 the base class's behavior, you'd get bugs almost anywhere you 
 used inheritance, since the derived class's data isn't being 
 modified properly.
Now I have the feeling, I'm missing something elementary... sorry for this... But take the classic example of OOP of an Animal and a Dog: Animal. Let the animal implement some default move and eat behavior. Let the dog override the move method and implement bark. If you degrade the dog to the animal by casting it should still be able to move and eat, but not bark. This should always be true for inherited objects as the base classes define enough content to manage proper behavior of their own. (Given they are not abstract, a function is not virtual and so on...)
Jan 04
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Friday, 4 January 2019 at 08:40:04 UTC, Alex wrote:
 class A
 {
 public:
     int foo(){return 1;}
 };
 class B : public A
 {
 public:
     int foo(){return 2;}
 };
In C++, methods are non-virtual by default. In D, they are virtual by default. Because of this, the two examples are different. In fact, D disallows overriding non-virtual, non-private functions, as specified in https://dlang.org/spec/function.html#virtual-functions. For private functions it does work, though: class A { private final int foo() { return 1; } } class B : A { final int foo() { return 2; } } unittest { assert((new A).foo == 1); assert((new B).foo == 2); assert((cast(A)(new B)).foo == 1); }
 Now I have the feeling, I'm missing something elementary... 
 sorry for this...
 But take the classic example of OOP of an Animal and a Dog: 
 Animal.
 Let the animal implement some default move and eat behavior.
 Let the dog override the move method and implement bark.
 If you degrade the dog to the animal by casting it should still 
 be able to move and eat, but not bark.
Imagine you give me a box with an Animal in it. You know it's a Bird, but I only know it's an Animal of some kind. Case 1: I tell it to move() to the top of a tree. Would you expect it to climb or fly? (let's not get into penguins and other flightless birds right now, the OOP animal metaphor is strained enough as it is) Case 2: I try to tell it to bark(), but there's no way to do that, because I have access to it as an Animal, not a Dog. Cutting off its beak and gluing a muzzle there (casting a Bird to a Dog) will only lead to suffering. Case 1 is overriding - Bird has defined how move() should work, and it will do that even if you only know it's an animal of some kind. Case 2 is subclassing - a Dog can do things that the average Animal can't, and this is reflected in it having a wider interface - more methods. This can be exemplified in D as: import std.stdio : writeln; abstract class Animal { abstract void move(); } class Bird : Animal { override void move() { writeln("Flying."); } } class Dog : Animal { override void move() { writeln("Walking."); // Assume this is not Muttley } void bark() { writeln("Woof!"); } } unittest { (new Bird).move(); // Flying (cast(Animal)new Bird).move(); // Flying //(cast(Animal)new Bird).bark(); // Fails to compile - generic animals can't bark (cast(Dog)new Bird).bark(); // Crashes because a Bird is not a Dog, cast returns null. } -- Simen
Jan 04
parent reply Alex <sascha.orlov gmail.com> writes:
On Friday, 4 January 2019 at 09:19:48 UTC, Simen Kjærås wrote:
 On Friday, 4 January 2019 at 08:40:04 UTC, Alex wrote:
 class A
 {
 public:
     int foo(){return 1;}
 };
 class B : public A
 {
 public:
     int foo(){return 2;}
 };
In C++, methods are non-virtual by default. In D, they are virtual by default. Because of this, the two examples are different. In fact, D disallows overriding non-virtual, non-private functions, as specified in https://dlang.org/spec/function.html#virtual-functions. For private functions it does work, though:
Ha... got it! Because final methods are allowed in interfaces, maybe this is the answer to the OP...
 class A {
     private final int foo() { return 1; }
 }
 class B : A {
     final int foo() { return 2; }
 }

 unittest {
     assert((new A).foo == 1);
     assert((new B).foo == 2);
     assert((cast(A)(new B)).foo == 1);
 }

 Imagine you give me a box with an Animal in it. You know it's a 
 Bird, but I only know it's an Animal of some kind.

 Case 1: I tell it to move() to the top of a tree. Would you 
 expect it to climb or fly?
 (let's not get into penguins and other flightless birds right 
 now, the OOP animal metaphor is strained enough as it is)

 Case 1 is overriding - Bird has defined how move() should work, 
 and it will do that even if you only know it's an animal of 
 some kind.

 This can be exemplified in D as:

 import std.stdio : writeln;

 abstract class Animal {
     abstract void move();
 }

 class Bird : Animal {
     override void move() {
         writeln("Flying.");
     }
 }


 unittest {
     (new Bird).move(); // Flying
     (cast(Animal)new Bird).move(); // Flying
 }

 --
   Simen
Case 2 is clear. I was wondering about the behavior in case 1: not because of the override behavior, but because there is an explicit cast in between. I assume the move method of an Animal is not abstract, and therefore I supposed, casting to this type explicitly should restore this very non-abstract behavior. But this is not the case. And the final/virtual thing above explains this to some extent, too... I think...
Jan 04
parent reply bauss <jj_1337 live.dk> writes:
On Friday, 4 January 2019 at 09:53:18 UTC, Alex wrote:
 I assume the move method of an Animal is not abstract, and 
 therefore I supposed, casting to this type explicitly should 
 restore this very non-abstract behavior. But this is not the 
 case.
 And the final/virtual thing above explains this to some extent, 
 too... I think...
A cast is only a way to tell the compiler the signature of a type. In most cases it actually does nothing at runtime and is just a way to help the compiler. Ex. auto b = new B; auto a = cast(A)b; Will just tell the compiler that all usage of a will be restricted to the signature of A. It doesn't tell the compiler that all usage of a should be the same as A. At runtime it would actually just be: auto b = new B; auto a = b;
Jan 04
parent reply Alex <sascha.orlov gmail.com> writes:
On Friday, 4 January 2019 at 09:58:59 UTC, bauss wrote:
 On Friday, 4 January 2019 at 09:53:18 UTC, Alex wrote:
 I assume the move method of an Animal is not abstract, and 
 therefore I supposed, casting to this type explicitly should 
 restore this very non-abstract behavior. But this is not the 
 case.
 And the final/virtual thing above explains this to some 
 extent, too... I think...
A cast is only a way to tell the compiler the signature of a type. In most cases it actually does nothing at runtime and is just a way to help the compiler. Ex. auto b = new B; auto a = cast(A)b; Will just tell the compiler that all usage of a will be restricted to the signature of A. It doesn't tell the compiler that all usage of a should be the same as A. At runtime it would actually just be: auto b = new B; auto a = b; //*
* The last line is thought to be restricted to the signature of A, but the behavior of B. Yeah... I think this is a matter of habituation. I assumed casting is something more powerful and overcomes the virtuality of functions if their body exists. But its the other way round. Thanks for the clarification. As for the OP, I think here the usefulness of ", D" should be visible: ´´´ interface D { int foo(); final int fun(){ return 42; } } class A { //size_t dummy; int foo() { return 1; } int fun() { return 72; } } class B : A, D { override int foo() { return 2; } } void main() { B b = new B(); assert(b.foo == 2); // returns 2 assert(b.fun == 72); // fun returns 72, as defined in A D d = cast(D) b; assert(d.foo == 2); // returns 2 assert(d.fun == 42); // fun returns 42, as defined in D A a = cast(A) b; assert(a.foo == 2); // returns 2, as it remains B assert(a.fun == 72); // fun returns 72, as defined in A D d2 = cast(D) a; assert(d2.foo == 2); // returns 2, as it remains B assert(d2.fun == 42); // fun returns 42, as defined in D A a2 = new A(); assert(a2.foo == 1); // returns 1, as defined in A assert(a2.fun == 72); // returns 72, as defined in A assert((cast(D) a2) is null); // not castable to D } ´´´
Jan 04
parent reply kdevel <kdevel vogtner.de> writes:
On Friday, 4 January 2019 at 11:27:59 UTC, Alex wrote:
 On Friday, 4 January 2019 at 09:58:59 UTC, bauss wrote:
[...]
 As for the OP, I think here the usefulness of ", D" should be 
 visible:
[...]
 class B : A, D
 {
     override int foo() { return 2; }
 }
[...]
     D d = cast(D) b;
     assert(d.foo == 2);             // returns 2
If I remove the ", D" the program segfaults in this line: Program received signal SIGSEGV, Segmentation fault. 0x000000000042c184 in D main () at ggg.d:26 26 d.foo(); (gdb) p d $1 = (ggg.D *) 0x0 There is clearly an assert (d); missing in the source. But why is d a null reference in the first place?
Jan 04
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/4/19 2:55 PM, kdevel wrote:
 On Friday, 4 January 2019 at 11:27:59 UTC, Alex wrote:
 On Friday, 4 January 2019 at 09:58:59 UTC, bauss wrote:
[...]
 As for the OP, I think here the usefulness of ", D" should be visible:
[...]
 class B : A, D
 {
     override int foo() { return 2; }
 }
[...]
     D d = cast(D) b;
     assert(d.foo == 2);             // returns 2
If I remove the ", D" the program segfaults in this line: Program received signal SIGSEGV, Segmentation fault. 0x000000000042c184 in D main () at ggg.d:26 26        d.foo(); (gdb) p d $1 = (ggg.D *) 0x0 There is clearly an    assert (d); missing in the source. But why is d a null reference in the first place?
Because when you dynamically cast one object or interface to another object or interface, and that result is not possible (if you remove ",D" from the example you quoted, then neither A nor B implement D), then the result is null. https://dlang.org/spec/expression.html#cast_expressions See parts 2 and 3. -Steve
Jan 04
parent reply kdevel <kdevel vogtner.de> writes:
On Friday, 4 January 2019 at 20:21:56 UTC, Steven Schveighoffer 
wrote:
 missing in the source. But why is d a null reference in the 
 first place?
Because when you dynamically cast one object or interface to another object or interface, and that result is not possible (if you remove ",D" from the example you quoted, then neither A nor B implement D), then the result is null.
I overlooked that Alex wrote class A and not class A : D which I came across in the examples in #10 and #11 of https://dlang.org/spec/interface.html and to which I referred to in my OP.
 https://dlang.org/spec/expression.html#cast_expressions

 See parts 2 and 3.
part 2 "Any casting of a class reference to a derived class reference is done with a runtime check to make sure it really is a downcast. null is the result if it isn't." Part 2 is about downcasting. Does that apply here? Due to the omission of ": D" in Alex' example the cast is not a cast "to a derived class reference" and hence this part does not apply. OTOH upcasting is also not covered by part 2 and seems to be legal: part 2 at its face value: ```part2.d import std.stdio; class A { void foo () { __FUNCTION__.writeln; } } class B : A { override void foo () { __FUNCTION__.writeln; } } void main () { auto x = new B; auto u = cast (A) x; typeof (u).stringof.writeln; u.foo; } ``` $ ./part2 A part2.B.foo
Jan 04
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/4/19 7:16 PM, kdevel wrote:
 On Friday, 4 January 2019 at 20:21:56 UTC, Steven Schveighoffer wrote:
 missing in the source. But why is d a null reference in the first place?
Because when you dynamically cast one object or interface to another object or interface, and that result is not possible (if you remove ",D" from the example you quoted, then neither A nor B implement D), then the result is null.
I overlooked that Alex wrote    class A and not    class A : D which I came across in the examples in #10 and #11 of    https://dlang.org/spec/interface.html and to which I referred to in my OP.
 https://dlang.org/spec/expression.html#cast_expressions

 See parts 2 and 3.
   part 2    "Any casting of a class reference to a derived class reference    is done with a runtime check to make sure it really is a downcast.    null is the result if it isn't." Part 2 is about downcasting. Does that apply here?
Yes. If the requested interface or class isn't a base class, or a base interface, then a downcast must be involved. Essentially the downcast is a runtime check to see if this class instance actually is a derived one, or a derived class implements the specified interface. D does not have multiple inheritance.
 Due to the omission
 of ": D" in Alex' example the cast is not a cast "to a derived class
 reference" and hence this part does not apply.
Of course it applies :) The fact that the class is not an instance of D in a derived class means that it returns null. The spec statement could be worded better.
 OTOH upcasting is also
 not covered by part 2 and seems to be legal:
Upcasting is trivial, and the compiler will not even spend time doing a runtime check, it knows how to do this at compile time. It doesn't even require a cast. Just like casting a short to an int doesn't require a cast, but you are free to put one in if you like. -Steve
Jan 07
prev sibling parent reply bauss <jj_1337 live.dk> writes:
On Friday, 4 January 2019 at 08:40:04 UTC, Alex wrote:
 On Friday, 4 January 2019 at 02:13:27 UTC, Neia Neutuladh wrote:
 I can't think of a single class system that works like that. 
 C++, Java, C#, Dart, and TypeScript all work like D here. 
 GObject in C works like D.
In the example below, the "2" of B.foo is printed only once. Independently of the cast type, after degrading a B to an A the degraded object behaves like an A. ´´´ C++ ´´´ #include <iostream> class A { public: int foo(){return 1;} }; class B : public A { public: int foo(){return 2;} }; void performFoo(A *a) { std::cout << a->foo() << std::endl; } int main() { auto a = new A(); auto b = new B(); std::cout << a->foo() << std::endl; std::cout << b->foo() << std::endl; std::cout << dynamic_cast<A*>(b)->foo() << std::endl; std::cout << static_cast<A*>(b)->foo() << std::endl; std::cout << reinterpret_cast<A*>(b)->foo() << std::endl; performFoo(a); performFoo(b); return 0; } ´´´
 The point of OOP is that a bundle of data has particular ways 
 of dealing with it. B has different data (at least 
 theoretically), and that data has different ways of working 
 with it. So if casting to a base class changed something to 
 use the base class's behavior, you'd get bugs almost anywhere 
 you used inheritance, since the derived class's data isn't 
 being modified properly.
Now I have the feeling, I'm missing something elementary... sorry for this... But take the classic example of OOP of an Animal and a Dog: Animal. Let the animal implement some default move and eat behavior. Let the dog override the move method and implement bark. If you degrade the dog to the animal by casting it should still be able to move and eat, but not bark. This should always be true for inherited objects as the base classes define enough content to manage proper behavior of their own. (Given they are not abstract, a function is not virtual and so on...)
Your C++ example is not the same as in D because in C++ functions aren't virtual by default, they are in D. Mark your functions as virtual in your C++ example and see what happens. All functions in D are virtual by default!
Jan 04
parent Alex <sascha.orlov gmail.com> writes:
On Friday, 4 January 2019 at 09:30:32 UTC, bauss wrote:
 Your C++ example is not the same as in D because in C++ 
 functions aren't virtual by default, they are in D.

 Mark your functions as virtual in your C++ example and see what 
 happens.

 All functions in D are virtual by default!
Yep. Got it! Thanks :)
Jan 04
prev sibling parent reply bauss <jj_1337 live.dk> writes:
On Friday, 4 January 2019 at 00:19:05 UTC, Alex wrote:
 On Friday, 4 January 2019 at 00:15:28 UTC, Neia Neutuladh wrote:
 On Thu, 03 Jan 2019 23:44:15 +0000, Alex wrote:
 I assume that is another bug and has nothing to do with 
 interfaces...
B.foo is both overriding A.foo and implementing D.foo, so that's not a bug.
I don't have any interfaces in my example. B.foo overrides A.foo. By casting a B object to be an A object, A's behavior should be granted, shouldn't it?
No, because you OVERRIDE A's foo(). A does not exist. A is B and when you cast B to A you just tell the compiler that the reference should only have A's signature available. You're not assigning B to A.
Jan 03
parent reply Alex <sascha.orlov gmail.com> writes:
On Friday, 4 January 2019 at 07:37:43 UTC, bauss wrote:
 No, because you OVERRIDE A's foo().

 A does not exist. A is B and when you cast B to A you just tell 
 the compiler that the reference should only have A's signature 
 available.

 You're not assigning B to A.
Let's assume this is right. How to force a B object to behave like an A object? I thought casting is a possible approach...
Jan 04
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 01/04/2019 12:46 AM, Alex wrote:
 On Friday, 4 January 2019 at 07:37:43 UTC, bauss wrote:
 No, because you OVERRIDE A's foo().

 A does not exist. A is B and when you cast B to A you just tell the
 compiler that the reference should only have A's signature available.

 You're not assigning B to A.
Let's assume this is right. How to force a B object to behave like an A object?
Not possible by default. However, a B object is already behaving like an A object because e.g. it overrides the foo() member function.
 I thought casting is a possible approach...
In this case, casting is using the B object through it's A interface. The overridden behavior does not change. (Actually, that is possible in languages that support multiple inheritance through multiple virtual function pointer tables (vtbl) but D does not support that.) Although I'm getting into implementation details here, I think it helps with understanding the semantics. There is only one vtbl per class object in D and the function entries are all filled in during construction. So, a B object's 'foo' slot in that table is filled with B.foo. So, such an object can foo() only as a B. If there is such a need and B can indeed support behaving like an A, it can do so itself by calling A.foo, not through vtbl, but directly: class B : A { override void foo() { A.foo(); // Calling directly } } By the way, do you have a use case in mind? Perhaps there are other ways to achieve that. Ali
Jan 04
next sibling parent Alex <sascha.orlov gmail.com> writes:
On Friday, 4 January 2019 at 21:08:24 UTC, Ali Çehreli wrote:
 [...]
 In this case, casting is using the B object through it's A 
 interface. The overridden behavior does not change.
Yeah... This was my mistake.
 (Actually, that is possible in languages that support multiple 
 inheritance through multiple virtual function pointer tables 
 (vtbl) but D does not support that.)

 Although I'm getting into implementation details here, I think 
 it helps with understanding the semantics. There is only one 
 vtbl per class object in D and the function entries are all 
 filled in during construction. So, a B object's 'foo' slot in 
 that table is filled with B.foo. So, such an object can foo() 
 only as a B.
Nice :)
 [...]
 By the way, do you have a use case in mind? Perhaps there are 
 other ways to achieve that.
Well, not really. I was just astonished that casting is not _the_ forge tool. Then, I checked, that the type indeed changes and wondered why the behavior was still from the object before casting. But I assume, as in an A-type an A object, as well as a B object, can be stored, and the creation is under my own control - a use case would be some corner case... And the default virtual definition explains the behavior very well...
Jan 04
prev sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 01/04/2019 01:08 PM, Ali Çehreli wrote:

 There is only one vtbl per class object
Correcting myself after reading Neia Neutuladh's post: I should have said "There is only one vtbl per class type". Every class object has a vtbl pointer that points at its type's vtbl. So, it's normally two pointer hops to execute a virtual function: 1) Get the object's vtbl pointer to reach the vtbl of that type; pseudo code: v = o.vtbl_ptr 2) Get the function pointer from a slot in that table (assuming foo() happens to be at index 5); pseudo code: f = v[5] // This must be a generic type like void* 3) Call the function by passing arguments; pseudo code: alias F = TheActualTypeOfTheMemberFunction; (cast(F)f)(42, "hello", ...) Ali
Jan 04
prev sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
On Fri, 04 Jan 2019 08:46:24 +0000, Alex wrote:
 Let's assume this is right. How to force a B object to behave like an A
 object? I thought casting is a possible approach...
It requires a bit of surgery: import std.stdio; class A { void foo() { writeln("hello from A!"); } } class B : A { override void foo() { writeln("hello from B!"); } } void main() { auto b = new B; auto ptrB = cast(void**)b; ptrB[0] = A.classinfo.vtbl.ptr; b.foo(); } This takes advantage of the object layout used by DMD. 'vtbl' is the virtual function table, which is basically an array of function pointers. Each member function defined in a type (and its super types) gets a unique index into that array. So when you write: b.foo(); That works out to: (cast(void function(B))b.vtbl[5])(b); We replace object b's vtbl with class A's, and now b is just an A with some extra stuff allocated in its memory block. Don't do this in production code. This is a terrible thing to do in production code, or any code you intend to use other than to see how D's object model works.
Jan 04
parent Alex <sascha.orlov gmail.com> writes:
On Friday, 4 January 2019 at 21:47:59 UTC, Neia Neutuladh wrote:
 On Fri, 04 Jan 2019 08:46:24 +0000, Alex wrote:
 Let's assume this is right. How to force a B object to behave 
 like an A object? I thought casting is a possible approach...
It requires a bit of surgery:
:)
     import std.stdio;
     class A
     {
         void foo() { writeln("hello from A!"); }
     }
     class B : A
     {
         override void foo() { writeln("hello from B!"); }
     }
     void main()
     {
         auto b = new B;
         auto ptrB = cast(void**)b;
         ptrB[0] = A.classinfo.vtbl.ptr;
         b.foo();
     }

 This takes advantage of the object layout used by DMD. 'vtbl' 
 is the virtual function table, which is basically an array of 
 function pointers. Each member function defined in a type (and 
 its super types) gets a unique index into that array.

 So when you write:

     b.foo();

 That works out to:

     (cast(void function(B))b.vtbl[5])(b);

 We replace object b's vtbl with class A's, and now b is just an 
 A with some extra stuff allocated in its memory block.

 Don't do this in production code. This is a terrible thing to 
 do in production code, or any code you intend to use other than 
 to see how D's object model works.
I see... That's cool nevertheless!
Jan 04
prev sibling parent Heromyth <bitworld qq.com> writes:
On Thursday, 3 January 2019 at 23:23:12 UTC, Neia Neutuladh wrote:
 On Thu, 03 Jan 2019 22:30:48 +0000, kdevel wrote:
 class A : D {
      int foo() { return 1; }
 }
 
 class B : A, D {
 [...]

 What is the meaning of the ", D"? It does not seem to make a 
 difference if it is omitted.
B must provide its own implementation of D. It can't simply use A's implementation.
As for class B, it has already included foo(), even if it doesn't override this method. So, is it necessary to override it again? It not always needed to override foo(). Sometimes, we just want to keep it as the one in class A and override it as necessary. Honestly hope that the compiler can do this.
Jan 09
prev sibling parent bauss <jj_1337 live.dk> writes:
On Thursday, 3 January 2019 at 22:30:48 UTC, kdevel wrote:
 https://dlang.org/spec/interface.html #11 has this code example:

 ```
 interface D
 {
     int foo();
 }

 class A : D
 {
     int foo() { return 1; }
 }

 class B : A, D
 {
     override int foo() { return 2; }
 }

 ...

 B b = new B();
 b.foo();            // returns 2
 D d = cast(D) b;
 d.foo();            // returns 2
 A a = cast(A) b;
 D d2 = cast(D) a;
 d2.foo();           // returns 2, even though it is A's D, not 
 B's D
 ```

 What is the meaning of the ", D"? It does not seem to make a 
 difference if it is omitted.
When you override foo() from A within B then you're omitting A's implementation. That's because all functions are virtual by default and thus you override the implementation. Basically you have something like (Simplified example) A: auto b = new B; b.v_table["foo"] = &b.super.foo; // If B doesn't override foo. (super = A) b.v_table["foo"] = &b.foo; // If B overrides foo. When you call foo() it basically looks it up in the v_table. Even if you cast B to A then the reference to A is still a reference to B but you just tell the compiler that the signature of your reference is A. So when you do the following:
 B b = new B();
 b.foo();            // returns 2
 D d = cast(D) b;
 d.foo();            // returns 2
 A a = cast(A) b;
 D d2 = cast(D) a;
 d2.foo();           // returns 2, even though it is A's D, not 
 B's D
It really translates to this: B b = new B(); b.foo(); D d = use_signature_of_D_on_a_reference_to_B(b); d.foo(); // Still calling b.foo() because D is not really a type and still a reference to B. A a = use_signature_of_A_on_a_reference_to_B(b); a.foo(); // Still calling b.foo() because B implement's A's signature by having it as a super class. This means that right now A is technically B and you just stripped away the signature of B to the signature of A. D d2 = use_signature_of_D_on_a_reference_to_A(a); d2.foo(); // Calls B.foo() because as stated earlier A is really B. There is no A actually ever existing in your scenario. Only a reference to B that different signatures are cast from.
Jan 03