Using the GLSL syntax in C++
I wrote custom vector classes such as vec2, vec3 etc. that mimic the GLSL types and look roughly like this:
struct vec3
{
inline vec3(float x, float y, float z)
: x(x), y(y), z(z) {}
union { float x, r, s; };
union { float y, g, t; };
union { float z, b, p; };
};
Operations on vectors are implemented this way:
inline vec3 operator +(vec3 a, vec3 b)
{
return vec3(a.x + b.x, a.y + b.y, a.z + b.z);
}
This allows me to create vectors and access their components using a GLSL-like syntax and perform operations on them almost as if they were numeric types. The unions allow me to refer to the first coordinate indifferently as x or as r, as is the case in GLSL. For instance:
vec3 point = vec3(1.f, 2.f, 3.f);
vec3 other = point + point;
point.x = other.b;
The problem of swizzling
But GLSL also allows swizzled access, even with holes between components. For instance p.yx behaves like a vec2 with p’s x and y swapped. When no component is repeated, it is also an lvalue. Some examples:
other = point.xyy; /* Note: xyy, not xyz */
other.xz = point.xz;
point.xy = other.xx + vec2(1.0f, 2.0f);
Now this could be done using standard getters and setters such as vec2 xy() and void xy(vec2 val). This is what the GLM library does.
Transparent getter and setter
However, I devised this pattern that lets me do exactly the same in C++. Since everything is a POD-struct, I can add more unions:
template<int I, int J> struct MagicVec2
{
friend struct vec2;
inline vec2 operator =(vec2 that);
private:
float ptr[1 + (I > J ? I : J)];
};
template<int I, int J>
inline vec2 MagicVec2<I, J>::operator =(vec2 that)
{
ptr[I] = that.x; ptr[J] = that.y;
return *this;
}
And eg. the vec3 class becomes (I simplified things a bit, for instance nothing prevents xx from being used as an lvalue here):
struct vec3
{
inline vec3(float x, float y, float z)
: x(x), y(y), z(z) {}
template<int I, int J, int K>
inline vec3(MagicVec3<I, J, K> const &v)
: x(v.ptr[I]), y(v.ptr[J]), z(v.ptr[K]) {}
union
{
struct { float x, y, z; };
struct { float r, g, b; };
struct { float s, t, p; };
MagicVec2<0,0> xx, rr, ss;
MagicVec2<0,1> xy, rg, st;
MagicVec2<0,2> xz, rb, sp;
MagicVec2<1,0> yx, gr, ts;
MagicVec2<1,1> yy, gg, tt;
MagicVec2<1,2> yz, gb, tp;
MagicVec2<2,0> zx, br, ps;
MagicVec2<2,1> zy, bg, pt;
MagicVec2<2,2> zz, bb, pp;
/* Also MagicVec3 and MagicVec4, of course */
};
};
Basically: I use a union to mix the vector’s floating-point components with a magic object which is not really a vec2 but can be cast implicitly to a vec2 (because there’s a vec2 constructor allowing it), and can be assigned a vec2 (because of its overloaded assignment operator).
I am very satisfied with the result. The GLSL code above works and I believe I get decent type safety. And I can #include a GLSL shader in my C++ code.
Limitations
Of course there are limitations. I know of the following ones:
sizeof(point.xz)will be3*sizeof(float)instead of the expected2*sizeof(float). This is by design and I do not know whether this could be problematic.&foo.xzcannot be used as avec2*. This should be OK because I only ever pass these objects by value.
So my question is: what may I have overlooked that will make my life difficult with this pattern? Also, I have not found this pattern anywhere else yet, so if anyone knows its name I am interested.
Note: I wish to stick to C++98, but I do rely on the compiler allowing type-punning through unions. My reason for not wanting C++11 yet is the lack of compiler support on several of my target platforms; all the compilers that are of interest to me support type punning, though.
In short: I think that it is difficult to make sure that this pattern works – that’s why you are asking. Moreover, this pattern could be replaced by a standard proxy pattern, for which correctness is easier to guarantee. I have to admit though that the storage overhead of a proxy-based solution is a problem when the proxies are created statically.
Correctness of the above code
This is code where there is no obvious bug; but paraphrasing C. A. R. Hoare, this is not code where there is obviously no bug. Moreover, how hard is it to convince oneself that there is no bug?
I do not see any reason why the pattern would not work – but it is not so easy to prove (even informally) that it will work. In fact, trying doing a proof could fail and point out to some problems.
To be safe, I would disable all implicitly-generated constructors/assignment operators for
MagicVecNclasses, just to avoid considering all the associated complications (see subsection below); however doing that is forbidden, because for union members one cannot override the implicitly defined copy assignment operator, as explained by the standard draft I have and by GCC’s error message:In the attached gist, I instead provide an implementation manually to be safe.
Note that MagicVec2’s assignment operator should accepts its parameter by const reference (see example below, where this works); implicit conversions still happen (the const reference will point to the created temporary; this would not work without the const qualifier).
Almost problems, but not quite
I thought a found a bug (which I didn’t), but it is still somewhat interesting to consider – just to see how many cases must be covered to rule out potential bugs. Would
p.xz = p.zxproduce the correct results? I thought thatMagicVec2‘s implicit assignment operator would be invoked, leading to incorrect results; in fact, it isn’t (I believe) becauseIandJare different and part of the type. What when the type is the same?p.xx = q.rris safe, butp.xx = p.rris tricky (even though it might be stupid, but it should still not corrupt memory): is the implicitly-generated assignment operatormemcpy-based? The answer seems to be no, but if yes, this would be amemcpybetween overlapping memory intervals, which is undefined behavior.UPDATE: An actual problem
As noticed by the OP, the default copy assignment operator is also invoked for the expression
p.xz = q.xz; in that case, it will in fact also copy the.ymember. As mentioned above, the copy assignment operator cannot be disabled or modified for datatypes which are part of an union.The proxy pattern
Moreover, I believe that there is a much simpler solution, namely the proxy pattern (which you are partially using).
MagicVecXshould contain a pointer to the containing class instead ofptr; this way you need no trick using unions.I tested this by compiling (but not linking) this code, which sketches the proposed solution: https://gist.github.com/1775054. Note that the code is not complete nor tested – one should also override the copy constructor of
MagicVecX.