Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to achieve vector swizzling in C++?

struct vec2
{
    union
    {
        struct { float x, y; };
        struct { float r, g; };
        struct { float s, t; };
    };
    vec2() {}
    vec2(float a, float b) : x(a), y(b) {}
};
struct vec3
{
    union
    {
        struct { float x, y, z; };
        struct { float r, g, b; };
        struct { float s, t, p; };
        // Here is the problem with g++.
        struct { vec2 xy; float z; };
        struct { float x; vec2 yz; };
    };
    vec3() {}
    vec3(float a, float b, float c) : x(a), y(b), z(c) {}
};

The code above compiles and works as expected in Visual Studio and so I can use it like

vec3 v1(1.f, 2.f, 3.f);
vec2 v2 = v1.yz; // (2, 3)

Not in g++ (MinGW).

src/main.cpp:22:23: error: member 'vec2 vec3::<unnamed union>::<unnamed struct>::xy' with constructor not allowed in anonymous aggregate
src/main.cpp:22:33: error: redeclaration of 'float vec3::<unnamed union>::<unnamed struct>::z'
src/main.cpp:18:30: note: previous declaration 'float vec3::<unnamed union>::<unnamed struct>::z'
src/main.cpp:23:32: error: member 'vec2 vec3::<unnamed union>::<unnamed struct>::yz' with constructor not allowed in anonymous aggregate
src/main.cpp:23:24: error: redeclaration of 'float vec3::<unnamed union>::<unnamed struct>::x'
src/main.cpp:18:24: note: previous declaration 'float vec3::<unnamed union>::<unnamed struct>::x'

I think I shouldn't be doing it like that in the first place. Any Ideas?

Edit: After reading a lot of articles and exploring open-source projects, I started to get how vector swizzling should be like and posted the solution below, still waiting for better answers though.

Edit 2: All vec* members must be accessed only from the parent like the GLM library.

like image 944
Beyondo Avatar asked Aug 01 '18 19:08

Beyondo


3 Answers

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.

like image 166
Beyondo Avatar answered Sep 21 '22 14:09

Beyondo


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 floats 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.

like image 24
xskxzr Avatar answered Sep 18 '22 14:09

xskxzr


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.

  1. 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.
  2. Structs are public by default, no need in public here. Classes, however, are private by default.
  3. 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).
  4. Other g++ options can be found here.

Hope that this will help you at least somehow.

like image 22
Shorrer Avatar answered Sep 22 '22 14:09

Shorrer