www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - traits: how to split parametrized type into basic type and parameters

reply Martin Kinkelin <noone spam.com> writes:
Hi,

I'm implementing a generic Vector!(uint d, T) struct (wrapping a T[d] array).
For readability:
alias Vector!(4u,float) float4;
alias Vector!(4u,int)   int4;

I'd like to be able to cast a float4 variable explicitly to an int4
(component-wise casting):
auto f4 = float4(1,2,3,4);
auto i4 = cast(int4) f4;

So when implementing opCast!NewType() in Vector!(d,T), I need a signature
constraint: NewType must be a Vector!(d2,T2), with d2 == d && isNumeric!T2.
The problem is that I don't know how to split NewType into the basic type
(Vector) and its parameters ({uint d2, T2}) as I didn't find anything related
in the std.traits documentation. I currently use a custom casting method, but
am not happy with it (I prefer the clean casting syntax preceding the
expression):
Vector!(d,T2) castTo(T2)() if (isNumeric!T2) { ... }
// e.g., auto i4 = f4.castTo!int();

Thanks in advance for any hints. Already falling in love with D after 2 days
of experimenting :)
Feb 13 2011
next sibling parent Martin Kinkelin <noone spam.com> writes:
I realize a parametrized constructor would be a better option:
this(T2)(const(Vector!(d,T2)) rhs) if (isNumeric!T2) { ... }
// e.g., auto i4 = int4(f4);

but I'd still prefer the casting syntax if possible.
Feb 13 2011
prev sibling next sibling parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
Hi Martin,

 I'm implementing a generic Vector!(uint d, T) struct (wrapping a T[d] arr=

 For readability:
 alias Vector!(4u,float) float4;
 alias Vector!(4u,int) =C2=A0 int4;

 I'd like to be able to cast a float4 variable explicitly to an int4
 (component-wise casting):
 auto f4 =3D float4(1,2,3,4);
 auto i4 =3D cast(int4) f4;

 So when implementing opCast!NewType() in Vector!(d,T), I need a signature
 constraint: NewType must be a Vector!(d2,T2), with d2 =3D=3D d && isNumer=

 The problem is that I don't know how to split NewType into the basic type
 (Vector) and its parameters ({uint d2, T2}) as I didn't find anything rel=

 in the std.traits documentation.

OK. First, to get access to a template parameters, the template must expose them by aliasing: template Vector(uint d, T) if (isNumeric!T) { alias d dim; alias T Type; T[d] values; } Now, d and T are externally accessible : alias Vector!(4, float) Float4; static assert(Float4.dim =3D=3D 4); static assert(is(Float4.Type =3D=3D int)); Now, there is no built-in way to see if a type is a template instantiation. A solution is to make a templated function do the work for you. Here we go: void vectorCheck(int d, T)(Vector!(d, T) v) {} vectorCheck can be instantiated only if passed a Vector!(someDim, SomeType). I used 'int d' due to some bugs with 'uint d'. Now, __traits offers the nifty 'compiles' instruction that checks at compile-time if a piece of code is valid D code. So we can create a template that checks if a type is indeed a Vector!(... , ...) template isVector(T) { static if (__traits(compiles, { void checkVector(int d, T)(Vector!(d,T) v) {} // define checkVector here checkVector(T.init); // use it with a value of type T: T.init })) // if the previous code compiles, then T is a Vector!(...,...) enum bool isVector =3D true; else enum bool isVector =3D false; } So, given a type T, isVector!T is true iff T is a Vector. Note that this template could easily be generalized: isInstanceOf!(someTemplateName, SomeType) Now, Vector V1 can be cast into a Vector V2 iff * V1.dim =3D=3D V2.dim * is(V1.Type : V2.Type) I used the U : V syntax here, which means U is a kind of V, or U derive from V or U can be cast into a V. Which gives us the last building block: template canBeCastInto(V1, V2) if (isVector!V1 && isVector!V2) { static if ((V1.dim =3D=3D V2.dim) && is(V1.Type : V2.Type)) // We know V1 and V2 are Vectors, we can use V1.dim and V1.Type enum bool canBeCastInto =3D true; else enum bool canBeCastInto =3D false; } Vector!(4,int) v1; Vector!(4,float) v2; assert( canBeCastInto!( typeof(v1), typeof(v2) )); // from int to float, = yes assert( ! canBeCastInto!( typeof(v2), typeof(v1) )); // no cast from float to int Of course, you can relax the type condition at your leisure. I mean, you can cast floats into ints if you want to. Also, you could cast small vectors into longer ones, putting the residual coordinates to Type.init. Philippe
Feb 14 2011
parent reply Martin Kinkelin <noone spam.com> writes:
Hi Philippe,

thank you very much! I added your isVector!T template as well as the two
aliases.
opCast!NewType is implemented like this:

V opCast(V)() if (isVector!V)
// parameters {d2,T2} of V are checked when instantiating V: d2 >= 1 &&
isNumeric!T2
{
    V r;
    foreach (i; 0 .. (d < V.dim ? d : V.dim))
        r._data[i] = cast(V.Type) _data[i];
    return r;
}

thereby allowing to cast to arbitrary component types as well as to arbitrary
dimensions. Works like a charm:

auto f3 = float3(1,2,3);
assert((cast(double2) f3) == double2(1,2));
assert((cast(int3) f3) == int3(1,2,3));
assert((cast(int4) f3) == int4(1,2,3,0));
Feb 14 2011
parent Martin Kinkelin <noone spam.com> writes:
 Did you try without the cast? Since we know that _data components can
 be cast to r._data elements, the compiler should take care of that for
 you.

Nope, I didn't try that because, for instance, floats shouldn't be implicitly castable to ints.
 You can also use cast(float2) f3 to get rid of the third coordinate.
 That's cool.

Yep, in case you want a copy. Additionally, I defined a to!(int d2) method which returns a reference to the lower-dimensional components (downcasting): ref Vector!(d2,T) to(int d2)() if (d2 <= d) { return *(cast(Vector!(d2,T)*) &this); } allowing for: auto f3 = float3(1,2,3); f3.to!2() = float2(-1,-2); assert(f3 == float3(-1,-2,3));
 You could also have a look at opAssign (for v1 = v2 expresions), and
 alias this.

Well, _data is private (and afaik I cannot modify a symbol's visibility via alias), so I overloaded the indexing operators (as well as most of the other operators as well). I only use _data here directly to aid the compiler and facilitate optimizations. But thanks for pointing 'alias ... this' out, I was looking for a concept similar to implicit type conversions in C++. The vector data is actually defined like this in Vector!(d,T): union { private T[d] _data; public struct { T x; static if (d >= 2) T y; static if (d >= 3) T z; static if (d >= 4) T w; } } So from the outside, components are accessed either via indexing or via x,y,z and w (analog to HLSL/GLSL). I also added some downcasting shortcuts via properties, e.g.: auto f4 = float4(1,2,3,4); auto r = f4.xyz + 2.0; // downcast f4 to float3 w/o copying, add double assert(r == double3(3,4,5)); // element type of r is double :) Martin
Feb 14 2011
prev sibling next sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Mon, Feb 14, 2011 at 13:09, Martin Kinkelin <noone spam.com> wrote:
 Hi Philippe,

 thank you very much!

You're welcome.
 I added your isVector!T template as well as the two aliases.
 opCast!NewType is implemented like this:

 V opCast(V)() if (isVector!V)
 // parameters {d2,T2} of V are checked when instantiating V: d2 >=3D 1 &&=

 {
 =C2=A0 =C2=A0V r;
 =C2=A0 =C2=A0foreach (i; 0 .. (d < V.dim ? d : V.dim))

Nice one, the compile-time ternary operator.
 =C2=A0 =C2=A0 =C2=A0 =C2=A0r._data[i] =3D cast(V.Type) _data[i];
 =C2=A0 =C2=A0return r;
 }

Did you try without the cast? Since we know that _data components can be cast to r._data elements, the compiler should take care of that for you.
 thereby allowing to cast to arbitrary component types as well as to arbit=

 dimensions. Works like a charm:

 auto f3 =3D float3(1,2,3);
 assert((cast(double2) f3) =3D=3D double2(1,2));
 assert((cast(int3) f3) =3D=3D int3(1,2,3));
 assert((cast(int4) f3) =3D=3D int4(1,2,3,0));

You can also use cast(float2) f3 to get rid of the third coordinate. That's cool. You could also have a look at opAssign (for v1 =3D v2 expresions), and alia= s this. Try putting: alias _data this; into your Vector code. That way a Vector will 'become' a T[d] when it makes sense. You gain direct access to the underlying coordinates: v[2] instead of v._data[2] This allows to use functions taking T[n] as an argument: pass them a Vector, it will expose its _data member. This should affect casting also. Another D idiom would be to define an .as!(SomeType) inner template that does the casting. Some people do not like cast. Though, as you saw, opCast can tightly control what kind of casting you authorize. struct Vector() { (...) auto as(Target)() if (isVector!Target && Target.dim < ... etc) { Target target; (fill the target) return target; } Very close to your opCast, really. Usage would be very close also: assert( f3.as!(double2) =3D=3D double2(1,2) ); assert( f3.as!(int3) =3D=3D int3(1,2,3) ); Philippe
Feb 14 2011
prev sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Mon, Feb 14, 2011 at 22:09, Martin Kinkelin <noone spam.com> wrote:

 So from the outside, components are accessed either via indexing or
 via x,y,z and w (analog to HLSL/GLSL). I also added some downcasting
 shortcuts via properties, e.g.:

 auto f4 = float4(1,2,3,4);
 auto r = f4.xyz + 2.0; // downcast f4 to float3 w/o copying, add double
 assert(r == double3(3,4,5)); // element type of r is double :)

We had some discussions on swizzling here, in the past. It makes for a nice use of opDispatch(string s).
Feb 14 2011