D casting broke?

Joerg Joergonson via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Tue Jun 21 08:35:08 PDT 2016


On Tuesday, 21 June 2016 at 02:39:25 UTC, H. S. Teoh wrote:
> On Tue, Jun 21, 2016 at 02:09:50AM +0000, Joerg Joergonson via 
> Digitalmars-d-learn wrote: [...]
>> Lets suppose A -> B means B is derived from A. That is, any 
>> object of B can be cast to A because the memory layout of A is 
>> contained in B and any object of B can be accessed as if it 
>> were an A.
>
> Correct.
>
>
>> Template parameters also can have this property since they are 
>> types.
> [...]
>
> The template *parameters* can also have this property. But the 
> *template* itself may not.  Contrived example:
>
> 	class A { int x; }
> 	class B : A { int y; }
>
> 	struct Templ(C : class) {
> 		int[C.sizeof] data;
> 	}
>
> 	Templ!A a;
> 	Templ!B b;
>
> 	a = b; // should this be allowed?
>
> It should be clear that allowing this would cause problems, 
> because in spite of the relationship between A and B, and hence 
> the relationship between the template arguments of a and b, the 
> same relationship does not hold between Templ!A and Templ!B 
> (note that .data is an array of ints, not ubytes, and may not 
> contain data in any layout that has any corresponds with the 
> relationship between A and B).
>
> Another contrived example:
>
> 	class A { int x; }
> 	class B : A { int y; }
>
> 	struct Templ(C : class) {
> 		static if (C.sizeof > 4) {
> 			string x;
> 		} else {
> 			float y;
> 		}
> 	}
>
> Allowing implicit casting from Templ!B to Templ!A would not 
> make sense, because even though the respective template 
> arguments have an inheritance relationship, the Templ 
> instantiation made from these classes has a completely 
> unrelated and incompatible implementation.
>

Well, I never mentioned any implicit casting. Obviously explicit 
casting wouldn't make sense either. It is a good example as it 
can show that Templ!A is completely different than Templ!B and no 
conversion is every possible even if A and B are related.

But I still think these are different examples.

You are talking about A!a vs A!b while I am talking about A!a vs 
B!b. I believe, but could be mistaken, that there is a subtle 
difference. I know it seems like B!b can be reduced to A!b, and 
the type system allows this... but if it never happens then all 
these cases explaining the problem of going from A!b to A!a are 
moot.

> Now granted, these are contrived examples, and in real-life we 
> may not have any real application that requires such strange 
> code. However, the language itself allows such constructs, and 
> therefore the compiler cannot blindly assume any relationship 
> between Templ!A and Templ!B even though there is a clear 
> relationship between A and B themselves.

I agree. I am not asking for blind assumptions. When I inform the 
compiler I want to cast to an object that I know should 
succeed(it was designed to do so) I don't expect a null. (As has 
been mentioned, I can do this using the void* trick, so there is 
a way)

> What should be done if we wish to allow converting Templ!B to 
> Templ!A, though?  One way (though this still does not allow 
> implicit casting) is to use opCast:
>
> 	struct Templ(C : class) {
> 		... // implementation here
>
> 		auto opCast(D : class)
> 			if (is(C : D)) // C must be a base class of D
> 		{
> 			...
> 			// do something here to make the conversion
> 			// valid. Maybe something as simple as:
> 			return cast(Templ!D) this;
>
> 			// (provided that there are no memory layout
> 			// problems in Templ's implementation, of
> 			// course).
> 		}
> 	}
>
> Implementing this using opCast actually gives us an even more 
> powerful tool: provided it is actually possible to convert 
> between potentially incompatible binary layouts of Templ!A and 
> Templ!B, the opCast method can be written in such a way as to 
> construct Templ!A from Templ!B in a consistent way, e.g., by 
> treating B as a subclass of A and calling the ctor of Templ!A, 
> or, in the case of my contrived examples, do something 
> non-trivial with the .data member so that the returned Templ!A 
> makes sense for whatever purpose it's designed for.  It allows 
> the implementor of the template to specify exactly how to 
> convert between the two types when the compiler can't possibly 
> divine this on its own.
>

While this is nice, the problem was how to convert. Even in 
opCast I would get a null and I wouldn't want to reconstruct A!a 
from B!b because that would essentially entail duplication. Of 
course, now I know I can just cast to void* then back and 
essentially trick/bypass the compilers type system checking.

> This is kind of bringing a nuclear warhead to an anthill, 
> though.  In my own code where I have a template wrapping around 
> types that need to convert to a common base type, I find it 
> more useful to use the following pattern instead:
>
> 	class A { ... }
> 	class B : A { ... }
>
> 	class WrapperBase {
> 		... // common stuff for all instantiations of Wrapper!X
> 	}
>
> 	class Wrapper(C : class) : WrapperBase {
> 		... // stuff specific to C
> 	}
>
> 	Wrapper!A a;
> 	Wrapper!B b;
> 	WrapperBase wb = a; // OK
> 	wb = b; // also OK
>
> This may or may not be what you're looking for, though.
>
>


This is exactly what I have done! ;) Just a bit more.

Essentially I have

class DoubleWrapper(D : C) : Wrapper!C

and the problem is that Wrapper contains overloads from 
WrapperBase that cast to Wrapper and when these overloads are 
called in DoubleWrapper(because I didn't overload anything in 
DoubleWrapper) the casts return null. (ecause DoubleWrapper!D 
cannot convert o Wrapper!C.

Basically I have to down cast something in WrapperBase to use it 
in Wrapper, but I know it is of type Wrapper when I am "in" 
Wrapper.  I also know it is of type DoubleWrapper when in 
DoubleWrapper. The cast sort of needs to be "polymorphic"... but, 
of course casts are static so this doesn't work.

In all the counter examples give, it's as if I had

class DoubleWrapper(D : C); instead of
class DoubleWrapper(D : C) : Wrapper!C

Or no DoubleWrapper at all. Again, I think there is some 
difference and some information provided by the inheritance that 
makes things different. It seems like a complete hack to have to 
cast to void* and back to make things work when there is a way.

For example, Your Templ class is very different, but 
DoubleWrapper can't do this and be *completely* different because 
it inherits Wrapper;

class DoubleWrapper(D : C)
{
    ... does something strange depending on different D...
}


Different than

class DoubleWrapper(D : C) : Wrapper!C
{
    ... does something crazy depending on different D...
    ... But cannot change the fact that it has a compatible memory 
footprint of Wrapper!C(since it inherits from it)
}



To take your example:


> 	class A { int x; }
> 	class B : A { int y; }
>
         class X(T : A);

> 	struct Templ(T : (B : A)) : X!A {
> 		static if (C.sizeof > 4) {
> 			string x;
> 		} else {
> 			float y;
> 		}

               // + There's still a complete "copy" of X!C lurking 
in here
> 	}

Different right? The X!C makes Templ!C different, regardless of 
the crazy stuff its does based on C, it still must be compatible 
with X!C because that's what inheritance tells us. We don't get 
that in your original example because there is no inheritance 
going on(well, excluding A and B)

In your original, Templ!A and templ!B may not be relatable, but 
in mine X!A and Templ!B are.

Now, I suppose you might be able to prove this wrong, say by 
constructing something funky in X:

class X(T : A)
{
     static if (T.sizeof > 4)
           int[] x;
     else
           string y;
}


But regardless, X!A is fixed w.r.t to Templ!B. So even though it 
is "crazy" depending on the template parameter, the craziness 
doesn't get though.

If we had

class Templ(T : B) : X!T

then it can be crazy just as Templ can be, but this is different. 
This has very little to do with A.

Basically there are constraints in the design that prevent 
craziness from taking place(I think). My Button!ButtonItem and 
Slider!SliderItem could have easily been coded as 
ButtonButtonItem and SliderSliderItem : ButtonButtonItem(although 
with increased obfuscation and reduced extensibility).


My point is, unless someone can prove me wrong, is in the actual 
problem I am presenting, not in the over simplified cases that 
exclude my case(In which case one would have to prove why the 
simplification holds rather than assuming and proving why the 
simplification is false), resorting to a void* cast is a hack and 
not necessary.

It should be quite logical for the compiler, possibly with a 
little help, to realize the cast works. e.g., 
Parallel_Cast(Button!ButtonItem)Slider!SliderItem.


(remember, Slider!(X : SliderItem) inherits from 
Button!ButtonItem directly! In fact, I could simply alias 
Button!ButtonItem to Q and then it inherits from Q... and so must 
always be castable to Q, right?)

e.g.,

class Button(T : ButtonItem);

alias Q = Button!ButtonItem;
class Slider(T : (SliderItem : ButtonItem)) : Q;

cast(Q)Slider!X;   // Should always work or no?

This shows in a pretty obvious way that Button!ButtonItem is 
static and hence is just regular inheritance.


Now, my case is slightly more complex:


class Slider(T : (SliderItem : ButtonItem)) : (Z!T : Q);

Z!T is a more general Button!ButtonItem, but it is still, at its 
base, a Q(hence the notation)... so always casting to Q should 
still work.

Q is a sort of "fixed point" and no matter what we build on top 
of it, no matter how complex, we should be able to cast down to 
it. Just like I can cast to Widget or WrapperBase because they 
are "fixed points"(and the kinda the reason why they are useful)

Again, it's possible I'm missing something or just being 
ignorant, but I don't think anyone has truly shown this(contrived 
examples are ok as long as they are analogous to the real problem 
and not oversimplified).

It would be nice to know, though, if this is just a short coming 
of D or if there a flaw in my logic. At least I could live with 
D's flaws ;)





More information about the Digitalmars-d-learn mailing list