D casting broke?

Joerg Joergonson via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Sun Jun 19 14:06:43 PDT 2016


On Sunday, 19 June 2016 at 20:18:14 UTC, Adam D. Ruppe wrote:
> On Sunday, 19 June 2016 at 19:59:28 UTC, Joerg Joergonson wrote:
>> This should be completely valid since B!T' obviously derives 
>> from A!T directly and we see that T' derives from b which 
>> derives from a directly. So B!b is an entirely derived from 
>> A!a and hence the cast should be successful
>
> I don't see how you think that. Here's the parent chain:
>
> B!b -> A!b -> X -> x -> Object
>
> There's no A!a in there, the cast is failing correctly.
>
> Just because `b` is a child of `a` doesn't mean that `A!b` is 
> the same as `A!a`. Consider an array:
>
> MyClass[] arr;
> arr ~= new MyClass(); // ok cool
>
> Object[] obj_arr = arr; // won't work! because...
> obj_arr[0] = new RandomClass(); // this would compile...
>
> // but obj_arr and arr reference the same data, so now:
> arr[0] is typed MyClass... but is actually RandomClass! It'd 
> crash horribly.
>
>
>
> Array is just one example of where converting A!b to A!a is 
> problematic. The same principle can apply anywhere, so it won't 
> implicitly cast them.
>

I'm not saying they are the same! They don't have to be the same. 
That is the whole point of inheritance and casting. A!b is 
derived from A!a if b is derived from a, is it not? If not, then 
I am wrong, if so then D casting has a bug.




>
>> The obviously question: Is there a simple way around this?
>
> What are you actually trying to do?

Do you really want to know? It's very simple and logical and 
might blow your mind and show you it's more complex than the 
example you have

I have a widget class

class Widget { Widget Parent; }

I have a button item class

class ButtonItem : Widget;

I have a button class

class Button : Widget { ButtonItem[] items; }

Make sense so far? Very logical and all that?

NOW, suppose I want to create a derived type from button? Say, a 
slider that effectively is a button that can move around:

class Slider : Button { }

So far so good, right?

WRONG! Slider shouldn't contain button items but slider items! 
How to get around this?


class SliderItem : ButtonItem; (since sliders are buttons slider 
items should be button items, right?)

So, to make this work we have to parameterize Button.

class Button(T : ButtonItem) : Widget { T[] items; }


So far so good!

and

class SliderItem : ButtonItem;

Very logical, Spock would be proud!

Now

class Slider(T : SliderItem) : Button!T;


Very logical still, right? Because T is of type SliderItem which 
is of type ButtonItem and therefor Button!SliderItem is of type 
Button!ButtonItem.

Everything works, right? Of course, I have a working example!

Slider!T is a type of Button!T, Slider!SliderItem is a type of 
Button!ButtonItem. Surely items in Button can hold SliderItems? 
(since they are derived from ButtonItems and ButtonItems work)

Ok, everything works!

Now what?

Well, In ButtonItem, I have to get the parent items to do some 
work. i.e.,

Work(Parent.items);

But this can't work because Parent is a Widget, so we must cast 
to a Button.

Work((cast(Button)Parent).items);

But this doesn't work because Button is parameterized. so

Work((cast(Button!T)Parent).items);

But this doesn't work because there is no T in ButtonItem, which 
is were we are at, so lets cast to a ButtonItem.

Work((cast(Button!ButtonItem)Parent).items);

This works!! At least as long as we are in ButtonItems!

When our parent is a Slider then the cast fails and everything 
goes to shit.

I have to duplicate the code AND only change the cast to 
cast(Slider!SliderItem)Parent and then everything works.


But, you might think that Slider!SliderItem is somehow not 
derived from Button!ButtonItem but it is, it was created to be 
that way by god himself.


Widget -> Button     -> Slider
              |             |
        -> ButtonItem -> SliderItem


First, for one, everything is an Widget, lets get that clear.

Second, Slider!SliderItem is just a wrapper to Button!ButtonItem. 
This allows us to add additional slider based code to a button to 
make it act like a slider(which is more than a button, but still 
a button).


This is just a 2D case of the 1D inheritance Slider is a Button. 
Just because we add a parameterization to it DOESN'T NECESSARILY 
change that. If the parameter also has an inheritance 
relationship then we have a fully valid inheritance relationship.


e.g., Slider!Pocahontas has only a partial inheritance to 
Button!ButtonItem because Pocahontas is not in any way derived 
from ButtonItem. But if Pocahontas is fully derived from 
ButtonItem then the partial inheritance is full inheritance.

Do you understand that?

Else, if you were correct, something like Slider!Widget and 
Button!Widget would never be relatable. Yet it's obvious that it 
is trivially relatable because Widget = Widget. In my case the 
only difference is SliderItem derives from ButtonItem.

We can always cast to a super class. ALWAYS! Slider!SliderItem is 
a super class of Button!ButtonItem.

In fact, if we had some way to do partial casting, we could write 
it this way

typeof(x = cast(Slider!ButtonItem)(SliderSliderItem)) = 
Slider!ButtonItem;

then

typeof(cast(Button!ButtonItem)x) = Button!ButtonItem


This may make more sense to you.

The first cast forces the items to look like button items, which 
is ok but slider items look like button items, right?

The second cast forces this entire thing now look like a Button, 
but that too is ok because a Slider is a Button.


Here's more code that shows where the partial casting fails:


import std.stdio;

class X
{
	X Parent;
}

class x : X
{

}

class a : x
{
	void Do()
	{
		auto pp = cast(B!b)Parent;
		if (pp !is null)
		{
			auto ppp = cast(A!b)pp;
			if (ppp !is null)
			{
				auto pppp = cast(A!a)ppp;
			}
		}
		auto p = cast(A!a)Parent;
		assert(p !is null);
	}

}


class A(T : a) : X
{
	X Parent = new X();
	T _a = new T();
}

class b : a { }
class B(T : b) : A!T { }

void main(string[] argv)
{
     auto _A = new A!a();
     auto _B = new B!b();
	_A.Parent = _A;
	_A._a.Parent = _A;
	_B.Parent = _B;
	_B._a.Parent = _B;
			
	_A._a.Do();

	_B._a.Do();
	
     auto xxx = cast(A!a)_B;	

	
}



As you can see, the problem is that D thinks A!b can't be cast to 
A!a. This is obviously easy to see that it can because

a[] Items;

can clearly hold objects of type b. D is ignoring this fact 
because it doesn't check inheritance on the parameters properly. 
It thinks they are unrelated and this is simply not true.

QED.























More information about the Digitalmars-d-learn mailing list