如何在 C++ 中实现 vector swizzling?

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) {}
};

上面的代码在 Visual Studio 中按预期编译和工作,所以我可以像

一样使用它
vec3 v1(1.f, 2.f, 3.f);
vec2 v2 = v1.yz; // (2, 3)

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'

我想我一开始就不应该那样做。有什么想法吗?

编辑: 在阅读了很多文章和探索开源项目后,我开始了解 vector swizzling 应该是什么样子并在下面发布解决方案,仍在等待更好的虽然答案。

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

至于"member with constructor not allowed in anonymous aggregate", ,是由于编译器运行符合旧标准,因为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)。在 g++ 编译器的参数中添加 -std=c++11,此错误可能会消失。

接下来。 g++ 唯一可以 maybe 编译你的代码的标志是 -fms-extensions-fvisibility-ms-compat 。匿名结构是 Microsoft 添加到其编译器中的 非标准 扩展。抱歉,我现在无法测试它,但我认为这可以解决问题。

还有一些额外的东西。

  1. 与 C 不同,您不应该 typedef C++ 中的结构 — 如果您命名了结构,则可以使用该名称作为类型来引用它们。
  2. 结构体默认为public,此处public不需要。 类,但是,默认情况下是私有的。
  3. 如果您的目的只是能够在 C++ 中使用 GLSL 数学,GLM 是实现此目的的方法。如果你想自己学习如何做,你可以参考他们的源代码(虽然它有很多模板)。
  4. 可以找到其他 g++ 选项 here

希望这至少能对您有所帮助。

首先,匿名结构是 a feature from C11, and is not allowed by C++,因此它不支持 class 具有构造函数的成员(不是 C 结构)。要编写可移植的 C++ 代码,您应该避免匿名结构:

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
};

现在代码可以在 GCC 下编译,但这还不够。在带有 -pedantic-errors 的 Clang 下,您将得到 several errors:

error: anonymous types declared in an anonymous union are an extension [-Werror,-Wnested-anon-types]

这是因为你不能在匿名联合中声明嵌套类型,所以你也应该将这些结构定义移到联合之外:

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} {}
};

尽管此解决方案有效,但您只能通过 v.xy.x 等方式访问成员,而不是简单的 v.x。此外,使用两个 floatvec2 进行别名会导致未定义的行为。我认为没有标准的解决方案可以完美地实现 vector swizzling。

对于非标准解决方案,可以使用不带构造函数的代理 class 而不是 vec2 来使编译器工作。 GLM library also uses this idea. OP has already posted an 作为这个想法的完整实现。

好吧,我自己找到了仅使用 C++ 标准的解决方案。
没有 命令行 也不使用 编译器特定的 代码。

所以这是我新的简单实现

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;
}

当然,您可以add/create 调配更多。现在有一个小测试。

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;
}

输出:

(30, 20, 10)
(-5, -5, 30)
(-5, 30)
(60, 70)
4200

如果您不像我在 gcc 中那样使用 IDE,则可以使用 std::cout 打印调试向量。