D casting broke?

H. S. Teoh via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Mon Jun 20 19:39:25 PDT 2016


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.

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.

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.

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.


T

-- 
It said to install Windows 2000 or better, so I installed Linux instead.


More information about the Digitalmars-d-learn mailing list