web-dev-qa-db-fra.com

Comment réaliser un vecteur swizzling en 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) {}
};

Le code ci-dessus est compilé et fonctionne comme prévu dans Visual Studio. Par conséquent, je peux l'utiliser comme

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

Pas en ++ (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'

Je pense que je ne devrais pas le faire comme ça en premier lieu. Des idées?

Edit: Après avoir lu de nombreux articles et exploré des projets open-source, j'ai commencé à comprendre à quel point vexer les vecteurs devrait être semblable et j'ai posté la solution ci-dessous, dans l'attente de meilleures réponses.

Edit 2: All vec* members doit être accessible uniquement à partir du parent comme leGLMlibrary.

12
X Stylish

Eh bien, j'ai moi-même trouvé la solution en utilisant uniquement les normes C++.
No lignes de commande ni en utilisant spécifique au compilateur code.

Voici donc ma nouvelle et simple implémentation

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

Bien sûr, vous pouvez ajouter/créer plus de swizzlings. Maintenant avec un petit 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;
}

En dehors:

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

Vous pouvez imprimer les vecteurs pour le débogage avec std::cout si vous n'utilisez pas unIDEcomme je l'ai fait dans gcc.

1
X Stylish

Tout d'abord, la structure anonyme est une fonctionnalité de C11 , et n'est pas autorisée par C++ , de sorte qu'elle ne prend pas en charge les membres de classe avec constructeurs (pas une structure C). Pour écrire du code C++ portable, vous devez éviter la structure anonyme:

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

Maintenant, le code est compilé sous GCC, mais cela ne suffit pas. Sous Clang avec -pedantic-errors, vous obtiendrez plusieurs erreurs :

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

En effet, vous ne pouvez pas déclarer un type imbriqué dans une union anonyme. Vous devez donc également déplacer ces définitions de structure en dehors de l'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} {}
};

Bien que cette solution fonctionne, vous pouvez uniquement accéder aux membres via, par exemple, v.xy.x, au lieu du simple v.x. De plus, créer un alias vec2 avec deux floats entraînerait un comportement indéfini. Je pense qu’il n’existe pas de solution standard pour réaliser parfaitement le vecteur swizzling. 

Pour les solutions non standard, on peut utiliser une classe proxy sans constructeurs au lieu de vec2 pour faire fonctionner le compilateur. La bibliothèque GLM utilise également cette idée. OP a déjà posté un answer en tant que mise en œuvre complète de cette idée. 

5
xskxzr

Quant à "membre avec constructeur non autorisé dans l'agrégat anonyme", est dû au compilateur fonctionnant conformément à l'ancienne norme, car à partir de C++ 11, les unions peuvent avoir des membres avec des constructeurs non triviaux (vous avez défini votre propre constructeur, ce n'est donc pas trivial, vous pouvez trouver des détails à ce sujet ici ). Ajoutez -std = c ++ 11 dans les arguments de votre compilateur g ++ et cette erreur aura probablement disparu.

Suivant. Les seuls indicateurs pour g ++ qui pourraient peut-être faire compiler votre code sont -fms-extensions et -fvisibility-ms-compat. Les structures anonymes sont une extension non standard que Microsoft a ajoutée à leur compilateur. Désolé, je ne peux pas le tester pour le moment, mais je pense que cela ferait l'affaire.

Et maintenant quelques extras.

  1. Contrairement à C, vous ne devriez pas typedef structs en C++ - si vous avez nommé vos structs, vous pouvez vous y référer en utilisant ce nom comme type.
  2. Les structures sont publiques par défaut, pas besoin dans public ici. Les classes, cependant, sont privées par défaut.
  3. Si votre intention est simplement de pouvoir utiliser les mathématiques GLSL en C++, GLM est le moyen de le faire. Si vous voulez apprendre à le faire vous-même, vous pouvez vous référer à leur code source (il est assez lourd avec des modèles, cependant).
  4. D'autres options g ++ peuvent être trouvées ici .

J'espère que cela vous aidera au moins d'une manière ou d'une autre.

2
Shorrer