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

2

3

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 {?

Namnama answered 6/5, 2022 at 6:50 Comment(12)
Unnamed structures are not a part of the standard C++ (i.e. struct {...}; with neither a type name nor a list of variables after it).Saxen
A union can not contain members that construct themselves. Which member of the union should be constructed?Sleepy
BTW: float elements[4] = { 0 }; only init the first element. I believe that is not what you want!Chaunce
Just remove Vector's ctor, it's useless. FYI what you are trying to do is UB, you cannot type pune between struct and an array - accessing inactive members of union which do not share to common initial sequence is UB.Adamsen
@Chaunce I don't think so. But the 0 in the braces can be omitted.Heracliteanism
Are the settings (standard, warnings) really equivalent for the compilers you used?Heracliteanism
@MartinYork yes it can. For 11 years already.Graiae
@Wolf: If the 0 is omitted, all values are initialized to default which is 0, right! But if you give an argument, it is passed in order to the elements. If only one is present, only one is set. Simply try it out with any other value as 0 to see the effect!Chaunce
@Chaunce If only one is present, only one is set. And the rest are value initialised. { 0 } is confusing and I would recommend against it, but has the same end result as {} or {0,0,0,0} in this case.Autolysis
@Chaunce timsong-cpp.github.io/cppwp/n4868/dcl.init.aggr#5.2Graiae
@Graiae if the union has a constructor that makes sure that it is clear what member is live?Sleepy
@MartinYork not necessarily some external to the union entity can do it. The usual pattern is to have an anonymous union in a struct which will handle everything in its special members.Graiae
A
3

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.

Autolysis answered 6/5, 2022 at 7:37 Comment(5)
@Heracliteanism What about the constructor of Vector3? What about the constructor? Removing the constructor wouldn't make the program valid C++ because it would still have anonymous struct that isn't allowed. The constructor itself isn't ill-formed.Autolysis
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.Namnama
@Autolysis The last sentence in your comment The constructor itself isn't ill-formed. is a bit confusing, I didn't' ask about the constructor itself but about getting rid of error: use of deleted function 'Plane::U::U()' (or the like).Heracliteanism
Is there a way to make this work if I replace struct { with struct Named { or similar?Namnama
@AaronFranke That will fix the issue of anonymous struct. But you still won't be able to access the inactive union member (the bonus part of my answer) which I'm assuming was your goal. My Pun template is a way to do that.Autolysis
S
0

This only fails to compile from Xcode when using gnu++98 (1998). It compiles fine in gnu++11 standard, and on

The root issue is the default ctor you've provided is non-trivial

Xcode compilation analysis

If you delete the constructor from your Vector3 struct, it will compile fine even in gnu++98

struct Vector3 {
  float x;
  float y;
  float z;
  //Vector3() {} // remove this line to compile
};
Succinate answered 15/4 at 20:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.