digitalmars.D - Parameterized inheritence issues, bug or ignorance?
- Joerg Joergonson (80/80) Jun 19 2016 I have a gui based on the following classes:
- David Nadlinger (13/29) Jun 19 2016 As has been pointed out in dm.D.learn already (which is where
- Joerg Joergonson (24/59) Jun 19 2016 So?
- David Nadlinger (8/12) Jun 19 2016 If you want to convert between class references by means of
- Kagamin (5/15) Jun 20 2016 What you ask for is called template covariance. D doesn't have it.
I have a gui based on the following classes: public class Widget { Widget Parent; } public class Item : Widget; public class Button(T : ButtonItem) : Widget { T[] Items; ... } public class ButtonItem : Item { void Do() { auto parent = (cast(Button!ButtonItem)this.Parent); ...} ... } All this works great! My gui works, all the buttons work, etc. I now want to add a new gui widget called a Slider: public class Slider(T : SliderItem) : Button!T { } public class SliderItem : ButtonItem { } Makes sense, right? Slider is derived from Button, and SliderItem is derived from ButtonItem. `Cause a Slider type is a derivation of a Button type it would seem we should logically derive all the parameters too. I wouldn't want a Slider using ButtonItem's... Seems a bit limiting as my slider's items have to behave like ButtonItems. Ok, But doing this, my code crashes for sliders. It does so on the cast. This is because SliderItem calls ButtonItem's Do since it wasn't overridden and so this.Parent is of type Slider!SliderItem. If I copy Do() to SliderButton and change the cast to cast(Slider!SliderItem) it works! That problem is that cast(Button!ButtonItem) returns null when this.Parent is of type Slider!SliderItem. My contention is that the cast should pass because both Slider and SliderItem are derived from Button and Button Item respectively. If we think of down casting as "If B is derived from A then cast(A)B" is valid, it would seem this should hold for parameterized types. It seems logical to me that Slider!SliderItem is completely derived from Button!ButtonItem. Just because it is parameterized doesn't change that. In fact, testing shows that the problem isn't the cast on the class type but on the parameter type. cast(Button!SliderItem)aSliderObject works. If we try to then cast that to ButtonItem, if fails: cast(Button!ButtonItem)[cast(Button!SliderItem)aSliderObject] The [...] passes and we end up with a Button!SliderItem type the our SliderItem's types are down cast to ButtonItems. Both should be valid! We can take a SliderItem x; and down cast x to a ButtonType so why can't we do it for a whole crap load of SliderItem objects that the class my use? Just because it is a parameter shouldn't change that. (BTW, I agree that it shouldn't work if SliderItem is not derived from ButtonItem. Please don't confuse that problem with this problem. SliderItem is derived from ButtonItem and that changes everything... just like it would if we had cast(ButtonItem)aSliderItem) So, Am I right in that this should work? Here is a simpler example import std.stdio; class a { } class b : a { } class A(T : a) { T x; } void main(string[] argv) { auto y = new A!a(); auto z = new A!b(); y.x = new a(); z.x = new b(); auto p1 = cast(A!a)y; auto p2 = cast(A!b)z; auto p3 = cast(A!a)z; // Fail! Why? z.x is of type b which can be down cast to type a without a problem -> auto p4 = cast(a)z.x; } The last line works, and effectively is the same as the p3. (we just cast the class type vs field type... but both ultimately go to the same type. e.g., p3.x = p4)
Jun 19 2016
On Monday, 20 June 2016 at 00:01:58 UTC, Joerg Joergonson wrote:class a { } class b : a { } class A(T : a) { T x; } void main(string[] argv) { auto y = new A!a(); auto z = new A!b(); y.x = new a(); z.x = new b(); auto p1 = cast(A!a)y; auto p2 = cast(A!b)z; auto p3 = cast(A!a)z; // Fail! Why? z.x is of type b which can be down cast to type a without a problem ->As has been pointed out in dm.D.learn already (which is where this discussion belongs), there isn't any inheritance relationship between A!b and A!a. In your example, it's easy to see that there can't be if you ask yourself whether the purported relation should be co- or contravariant in nature. To put it in simple terms, if your cast succeeded, you could do this: --- p3.x = new a; // Now typeof(z.x) == b, but it points to an instance of a! --- — David
Jun 19 2016
On Monday, 20 June 2016 at 00:15:33 UTC, David Nadlinger wrote:On Monday, 20 June 2016 at 00:01:58 UTC, Joerg Joergonson wrote:So? auto f = 1f; auto x = cast(int)f; f is float, x is an int, that's what casts do when you cast beyond their type. I mean, after all, p3.x is of type A!a and points to the same location as something that maps to A!b. It's no doubt going to be a problem if you stick something less in there than you were suppose do. If the point is: Too keep programmers from making mistakes. Then that's fine. But that doesn't mean in all cases is is bad. Sure I can down cast to a ButtonItem then store ButtonItem's in something that's actually a SliderItem, which is effectively upcasting behind the scenes... But that's my fault. It can be done outside inheritance too. Take a pointer to an float and write an int value to it... Same problem. If this is a restriction, then fine. Then the question is, how to get around it. I don't do anything that causes problem. I only iterate over the base type and never assign a base type to something that might contain a derived type. Can I just force the cast in some way if I know good an well it works for ever? Or do I have to write extra code to get around axiom: "We did this to save you trouble because we know better than you what you are doing. Go forth and write more code!"?class a { } class b : a { } class A(T : a) { T x; } void main(string[] argv) { auto y = new A!a(); auto z = new A!b(); y.x = new a(); z.x = new b(); auto p1 = cast(A!a)y; auto p2 = cast(A!b)z; auto p3 = cast(A!a)z; // Fail! Why? z.x is of type b which can be down cast to type a without a problem ->As has been pointed out in dm.D.learn already (which is where this discussion belongs), there isn't any inheritance relationship between A!b and A!a. In your example, it's easy to see that there can't be if you ask yourself whether the purported relation should be co- or contravariant in nature. To put it in simple terms, if your cast succeeded, you could do this: --- p3.x = new a; // Now typeof(z.x) == b, but it points to an instance of a! --- — David
Jun 19 2016
On Monday, 20 June 2016 at 00:34:18 UTC, Joerg Joergonson wrote:Can I just force the cast in some way if I know good an well it works for ever? Or do I have to write extra code to get around axiom: "We did this to save you trouble because we know better than you what you are doing. Go forth and write more code!"?If you want to convert between class references by means of pointer repainting, you can do so by casting to void* in between. As a side note, you might want to revisit your OOP terminology – in both this and the dm.D.learn thread, you are referring to down/upcasting in the reverse sense compared to the common definition. — David
Jun 19 2016
On Monday, 20 June 2016 at 00:01:58 UTC, Joerg Joergonson wrote:I have a gui based on the following classes: public class Widget { Widget Parent; } public class Item : Widget; public class Button(T : ButtonItem) : Widget { T[] Items; ... } public class ButtonItem : Item { void Do() { auto parent = (cast(Button!ButtonItem)this.Parent); ...} ... }What you ask for is called template covariance. D doesn't have it. You can solve your problem by providing Button!ButtonItem-specific functionality in an intermediate ButtonBase class: https://dpaste.dzfl.pl/4c071b237aa0
Jun 20 2016