D casting broke?
Joerg Joergonson via Digitalmars-d-learn
digitalmars-d-learn at puremagic.com
Mon Jun 20 14:33:25 PDT 2016
On Monday, 20 June 2016 at 10:38:12 UTC, ag0aep6g wrote:
> On 06/20/2016 01:40 AM, Joerg Joergonson wrote:
>> public class Button(T : ButtonItem) : Widget { ... }
>> public class ButtonItem : Item
>> {
>> void Do() { auto parent =
>> (cast(Button!ButtonItem)this.Parent); }
>> ...
>> }
>>
>> All this works great! As long as Do is not being called from a
>> derived
>> class
>>
>> public class Slider(T : SliderItem) : Button!T { }
>> public class SliderItem : ButtonItem { }
>>
>>
>> The last two classes are truly empty. Now, when I use a Slider
>> object,
>> things go to shit because the cast is invalid. this.Parent is
>> of type
>> Slider!SliderItem.
>
> It's the same setup as with the A and B things, right?
>
> Parent is a Widget that holds a Slider!SliderItem. That's fine
> because Slider!SliderItem is derived from Button!SliderItem
> which is derived from Widget.
>
> But Button!SliderItem does not derive from Button!ButtonItem.
> They both derive from Widget. So the cast fails.
>
> But you think it should succeed, of course.
>
> Is your position that Button!SliderItem should derive/inherit
> from Button!ButtonItem, enabling the cast, or do you suppose
> the cast should succeed because the fields are compatible?
>
> I.e., should this work?
>
> class A {int x;}
> class B {int x;}
> A a;
> B b = cast(B) a;
>
No, not at all. first, A and B are not related, so casting makes
no sense unless there is a conversion(opCast) or whatever, but
that is done by the user.
This is exactly opposite of what I am talking about.
>> SliderItem only sets the array type. So in Slider, I end up
>> with a
>> SliderItem[] type then in ButtonItem's Do(which gets called
>> since
>> SliderItem doesn't override), it tries to cast that down to a
>> ButtonItem. It should work. There is no reason it shouldn't
>> logically.
>> There is no up casting.
>
> Some terminology clarification: Casting from SliderItem to
> ButtonItem is upcasting. The other direction would be
> downcasting. Upcasting a single object is trivial and can be
> done implicitly. Downcasting must be done explicitly and may
> yield null.
>
> You say that you cast from SliderItem to ButtonItem. But that's
> not what's done in your snippet above. You try to cast from
> Button!SliderItem to Button!ButtonItem. Completely different
> operation.
Ok, I might have used terminology backwards.
The problem is more complex then maybe I demonstrated and anyone
has mentioned. Yes, there might be an issue with
downcasting/contravariance and all that. I think those problems
though, are general issues.
The real issue is that Slider!SliderItem doesn't override a
method that is called when a Slider!SliderItem object is used.
The method, in Button!ButtonItem casts a Widget to
Button!ButtonItem just fine because inside Button!ButtonItem, the
Widget is of type Button!ButtonItem.
When we are inside a Slider!SliderItem though, the same code is
executed with the same cast(using Button!ButtonItem) and this
fails because if it succedded we could potentially store
ButtonItems as SliderItems(being an "downcast", or similar to the
example you gave).
This is the code that has the problem.
It is used inside ButtonItem
auto parent = (cast(cButton!cButtonItem)this.Parent);
and not overridden in SliderItem, but still executed in there at
some point.
this.Parent is a Slider!SliderItem and I need the cast to work so
I can access the Item array.
But in Slider the array is of type SliderItem, not ButtonItem as
I initially thought, because I particularized it.
Hence there is a "hidden" downcast going on. Now, in my case, it
doesn't matter because I never store items in the wrong type. The
code is automatically generated and creates the correct type for
the correct storage class. I realize now though that it is
possible that it can be done(If I just appended a ButtonItem to
the array in ButtonItem, then when SliderItem is called, then
"non-overridden" method will store a ButtonItem in the SliderItem
array.
So, this isn't really a problem with casting so much as it is
with the complexity of the inheritence. By doing it the way I
did, to try to keep the Types and parameters synced and because
they inherit from each other, there can be problems.
To get what I want, which is probably impossible, I'd need the
cast to automatically cast in the correct type depending on where
it is being executed:
auto parent = (cast(typeof(parent)!this)this.Parent);
Which, of course, is impossible to do at compile time.
I only need parent to check if it's items exist in the array
if (parent.HoveredItems.canFind(this))
That is all it is used for, so there is no problem with it, but
if I don't cast I obviously can't access the HoverdItems... but
then the cast breaks for derived classes and parent is null.
To make it work I'd have to add, say, something like
containsHovered to Widget. Then I wouldn't need the cast, but
this doesn't make a lot of sense, since Widget doesn't contain an
array of HoveredItems.
Alternatively I could add an interface to inherit that contains
something like that and it would work...
Regardless though, it requires adding a lot of extra code
duplication just to get the cast "to pass" when the only thing it
is to prevent is storing a less derived type in a more derived
type.... which I never do. I don't even store any types in
anything. Everything is setup from the get go and pretty much
static. (Although, again, I see that in general this is not the
case)
I still think it is a grey area though.
e.g., List<string> and List<Mystring> may be problemmatic, I
should still be able to cast List<Mystring> to List<string> and
use elements, but not store them.
so something like
cast(out Button!ButtonItem)sliderSliderItem; could work, the out
being obvious that sliderSliderItem is never used to store
ButtonItems.
In any case, D can't do this easily it seems so it's all moot. I
just copied and pasted the code and changed the cast. It lets me
get on with life.
Thanks.
More information about the Digitalmars-d-learn
mailing list