How to achieve vector swizzling in C++?
First, anonymous struct is a feature from C11, and is not allowed by C++, so it does not support class members with constructors (not a C struct). To write portable C++ code, you should avoid anonymous struct:
struct vec2 // use C++ style struct declaration
{
// struct is public by default
union
{
struct { float x, y; } xy; // add member name,
struct { float r, g; } rg; // now the declaration declares a member
struct { float s, t; } st; // instead of an anonymous struct
};
vec2() {}
vec2(float a, float b) : xy{a, b} {}
// ^^^^^^^^ also change the initialization
};
struct vec3
{
public:
union
{
struct { float x, y, z; } xyz; //
struct { float r, g, b; } rgb; //
struct { float s, t, p; } stp; // add member name
struct { vec2 xy; float z; } vecz; //
struct { float x; vec2 yz; } xvec; //
};
vec3() {}
vec3(float a, float b, float c) : xyz{a, b, c} {}
// ^^^^^^^^ also change the initialization
};
Now the code compiles under GCC, but that's not enough. Under Clang with -pedantic-errors
, you'll get several errors:
error: anonymous types declared in an anonymous union are an extension [-Werror,-Wnested-anon-types]
This is because you cannot declare a nested type in an anonymous union, so you should also move these struct definitions outside the union:
struct vec2
{
struct XY { float x, y; };
struct RG { float r, g; };
struct ST { float s, t; };
union
{
XY xy;
RG rg;
ST st;
};
vec2() {}
vec2(float a, float b) : xy{a, b} {}
};
struct vec3
{
struct XYZ { float x, y, z; };
struct RGB { float r, g, b; };
struct STP { float s, t, p; };
struct VECZ { vec2 xy; float z; };
struct XVEC { float x; vec2 yz; };
union
{
XYZ xyz;
RGB rgb;
STP stp;
VECZ vecz;
XVEC xvec;
};
vec3() {}
vec3(float a, float b, float c) : xyz{a, b, c} {}
};
Although this solution works, you can only access the members via, for example, v.xy.x
, instead of simple v.x
. In addition, aliasing vec2
with two float
s would result in undefined behavior. I think there is no standard solution to achieve vector swizzling perfectly.
For non-standard solution, one can use a proxy class without constructors instead of vec2
to make the compiler work. The GLM library also uses this idea. OP has already posted an answer as a complete implementation of this idea.
Well, I've found the solution myself using only the C++ Standards.
No command-lines neither using compiler-specific code.
So this is my new and simple implementation
template<unsigned int I>
struct scalar_swizzle
{
float v[1];
float &operator=(const float x)
{
v[I] = x;
return v[I];
}
operator float() const
{
return v[I];
}
float operator++(int)
{
return v[I]++;
}
float operator++()
{
return ++v[I];
}
float operator--(int)
{
return v[I]--;
}
float operator--()
{
return --v[I];
}
};
// We use a vec_type in a template instead of forward declartions to prevent erros in some compilers.
template<typename vec_type, unsigned int A, unsigned int B>
struct vec2_swizzle
{
float d[2];
vec_type operator=(const vec_type& vec)
{
return vec_type(d[A] = vec.x, d[B] = vec.y);
}
operator vec_type()
{
return vec_type(d[A], d[B]);
}
};
struct vec2
{
union
{
float d[2];
scalar_swizzle<0> x, r, s;
scalar_swizzle<1> y, g, t;
vec2_swizzle<vec2, 0, 0> xx;
vec2_swizzle<vec2, 1, 1> yy;
};
vec2() {}
vec2(float all)
{
x = y = all;
}
vec2(float a, float b)
{
x = a;
y = b;
}
};
/* Debugging */
inline std::ostream& operator<<(std::ostream &os, vec2 vec)
{
os << "(" << vec.x << ", " << vec.y << ")";
return os;
}
template<typename vec_type, unsigned int A, unsigned int B, unsigned int C>
struct vec3_swizzle
{
float d[3];
vec_type operator=(const vec_type& vec)
{
return vec_type(d[A] = vec.x, d[B] = vec.y, d[C] = vec.z);
}
operator vec_type()
{
return vec_type(d[A], d[B], d[C]);
}
};
struct vec3
{
union
{
float d[3];
scalar_swizzle<0> x, r, s;
scalar_swizzle<1> y, g, t;
scalar_swizzle<2> z, b, p;
vec2_swizzle<vec2, 0, 1> xy;
vec2_swizzle<vec2, 1, 2> yz;
vec3_swizzle<vec3, 0, 1, 2> xyz;
vec3_swizzle<vec3, 2, 1, 0> zyx;
};
vec3() {}
vec3(float all)
{
x = y = z = all;
}
vec3(float a, float b, float c)
{
x = a;
y = b;
z = c;
}
};
/* Debugging */
inline std::ostream& operator<<(std::ostream &os, vec3 vec)
{
os << "(" << vec.x << ", " << vec.y << ", " << vec.z << ")";
return os;
}
Of course, you can add/create more swizzlings. Now with a little test.
int main()
{
vec3 v0(10, 20, 30);
std::cout << v0.zyx << std::endl;
vec2 c(-5, -5);
v0.xy = c;
vec2 v1(v0.yz);
std::cout << v0 << std::endl;
std::cout << v1 << std::endl;
vec3 v(50, 60, 70);
vec2 d = v.yz;
std::cout << d << std::endl;
float f = d.x * d.y;
std::cout << f << std::endl;
return 0;
}
Out:
(30, 20, 10)
(-5, -5, 30)
(-5, 30)
(60, 70)
4200
You can print the vectors for debugging with std::cout
if you're not using an IDE as I did in gcc.
As for "member with constructor not allowed in anonymous aggregate", , is due to compiler running in compliance with older standard, because as of C++11, unions can have members with non-trivial constructors (you defined your own constructor, so it's non-trivial, details about this can be found here). Add -std=c++11 in your g++ compiler's arguments and this error will likely be gone.
Next. The only flags for g++ that could maybe make it compile your code are -fms-extensions and -fvisibility-ms-compat. Anonymous structs are a non-standard extension that Microsoft added to their compiler. Sorry, right now I can't test it, but I think that would do the trick.
And now some extras.
- Unlike in C, you shouldn't
typedef
structs in C++ — if you named your structs, you can refer to them using that name as type. - Structs are public by default, no need in
public
here. Classes, however, are private by default. - If your intent is to just be able to use GLSL math in C++, GLM is the way to do it. If you want to learn how to do it yourself you can refer to their source code (it is quite heavy with templates, though).
- Other g++ options can be found here.
Hope that this will help you at least somehow.