c++gccstructlanguage-lawyerunions

C++ union struct with struct member works on Clang and MSVC but not GCC


I am trying to define a union struct with some struct and primitive members overlapping in memory with a simple array. This works perfectly in Clang and MSVC, but it doesn't compile with GCC (G++).

struct Vector3 {
    float x;
    float y;
    float z;
    Vector3() {}
};

struct Plane {
    union {
        struct {
            Vector3 normal;
            float d;
        };
        float elements[4] = { 0 };
    };
    Plane() {}
};

With GCC, I get this compile error:

<source>:11:33: error: member 'Vector3 Plane::<unnamed union>::<unnamed struct>::normal' with constructor not allowed in anonymous aggregate
   11 |                         Vector3 normal;
      |                                 ^~~~~~

Is the code example I gave valid C++? Why specifically is it not allowed in an anonymous aggregate, but it seems to work in a named one? What can I change about it to make it work in GCC that doesn't involve deleting the constructors or naming the struct in the union? What is the reason that it works in Clang and MSVC but not in GCC?

Is there a way to make it work if I replace struct { with struct Named {?


Solution

  • Is the code example I gave valid C++?

    No. Anonymous structs are not allowed, so the program is ill-formed.

    What is the reason that it works in Clang and MSVC

    When an ill-formed program works, it is often due to a language extension.

    but not in GCC

    Differences in implementation of similar language extension perhaps. The limitations of such extension are not defined by the language of course. Since this extension is based on a C language feature, it sort of makes sense that it doesn't necessarily work with C++ features such as constructors.

    What can I change about it to make it work in GCC that doesn't involve deleting the constructors or naming the struct in the union?

    Only way to make the program well defined C++ is to not use an anonymous struct.


    Bonus answer: If you were hoping to read elements after having written to normal or d or vice versa, then that's not allowed either. The behaviour of the program would be undefined.


    How can I make differently named properties with overlapping memory? Aside from Plane, I also want to do this in other structs, such as by having a 3D Basis struct columns[3] with the array's members also accessible via x, y, and z.

    C++ is limited in this regard and it cannot be done in a simple way. It can be done with a bit of complexity by relying on operator overloads:

    template<class T, std::size_t size, std::size_t i>
    struct Pun {
        T a[size];
        static_assert(i < size);
        auto& operator=(T f) { a[i] = f; return *this; }
        operator       T&()        &   { return a[i]; }
        operator const T&()  const &   { return a[i]; }
        operator       T ()        &&  { return a[i]; }
        T      * operator&()       &   { return a+i ; }
        T const* operator&() const &   { return a+i ; }
    };
    
    template<class T, std::size_t size>
    struct Pun<T, size, size> {
        T a[size];
        using A = T[size];
        operator       A&()        &   { return a; }
        operator const A&()  const &   { return a; }
        A      * operator&()       &   { return &a; }
        A const* operator&() const &   { return &a; }
    };
    
    union Plane {
        Pun<float, 4, 4> elements;
        Pun<float, 4, 0> x;
        Pun<float, 4, 1> y;
        Pun<float, 4, 2> z;
        Pun<float, 4, 3> d;
    };
    

    Reading inactive members of Plane is allowed, because all elements are layout compatible structs. x etc. can implicitly convert to float and elements can implicitly convert to an array of float.