20

How would you go about doing this in standard C++11/14 ? Because if I'm not mistaken this isn't standard compliant code with the anonymous structs.

I wish to access the members the same way as you would with this.

template <typename some_type>
struct vec
{
    union {
        struct { some_type x, y, z; };
        struct { some_type r, g, b; };

        some_type elements[3];
    };
};
timrau
  • 22,578
  • 4
  • 51
  • 64
Grandstack
  • 323
  • 1
  • 2
  • 7
  • If you insist on being able to write `vec t; t.x = 10;` then you can give it reference members `x`, `y`, `z` etc. and initialize them to refer to the corresponding member of `elements`, but this will increase the struct's size considerably. If you can tolerate `t.x() = 10`, then make them member functions. – T.C. Aug 28 '14 at 07:36
  • 1
    But I think that `vec.x = 42; assert(vec.r == 42);` is not guaranty anyway by the standard (but compiler may). – Jarod42 Aug 28 '14 at 08:06
  • @Jarod42 N3936 [class.mem]/18: "If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. ..." – Casey Aug 28 '14 at 15:22
  • For ACCESSING the members the sameway as with non-standard anonymous struct, give a name to your struct, like Praetorian told you, AND define inside your template type a reference for each memeber of the anonymous struct: `struct vec{ ..... some_type &x=a.x; ...};` You can the refer to v.x instead of v.a.x – Christophe Aug 28 '14 at 17:22

3 Answers3

16

Yes, neither C++11 nor C++14 allow anonymous structs. This answer contains some reasoning why this is the case. You need to name the structs, and they also cannot be defined within the anonymous union.

§9.5/5 [class.union]

... The member-specification of an anonymous union shall only define non-static data members. [ Note: Nested types, anonymous unions, and functions cannot be declared within an anonymous union. —end note ]

So move the struct definitions outside of the union.

template <typename some_type>
struct vec
{
    struct xyz { some_type x, y, z; };
    struct rgb { some_type r, g, b; };

    union {
        xyz a;
        rgb b;
        some_type elements[3];
    };
};

Now, we require some_type to be standard-layout because that makes all the members of the anonymous union layout compatible. Here are the requirements for a standard layout type. These are described in section §9/7 of the standard.

Then, from §9.2 [class.mem]

16   Two standard-layout struct (Clause 9) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible types (3.9).
18   If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.

And for the array member, from §3.9/9 [basic.types]

... Scalar types, standard-layout class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called standard-layout types.

To ensure that some_type is standard layout, add the following within the definition of vec

static_assert(std::is_standard_layout<some_type>::value, "not standard layout");

std::is_standard_layout is defined in the type_traits header. Now all 3 members of your union are standard layout, the two structs and the array are layout compatible, and so the 3 union members share a common initial sequence, which allows you to write and then inspect (read) any members belonging to the common initial sequence (the entire thing in your case).

Community
  • 1
  • 1
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 1
    The array isn't a standard-layout struct, though. – T.C. Aug 28 '14 at 07:33
  • @T.C. I just realized that as I was re-reading after posting (should've done that before). Trying to find something that says that it is, seems silly if the type is standard layout, an array isn't guaranteed to be. – Praetorian Aug 28 '14 at 07:35
  • @Praetorian does the standard guarantee that `elements[0]` will be the same as `a.x` (and `b.r`)? Can't find it. – Anton Savin Aug 28 '14 at 07:56
  • @AntonSavin §9.2/19 *If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member.* That combined with all 3 elements being layout compatible guarantees that they start at the same address. – Praetorian Aug 28 '14 at 07:58
  • @Praetorian it only guarantees that for `vec v` `&v == &v.a`. – Anton Savin Aug 28 '14 at 08:04
  • There's still no guarantee that a standard-layout array of `X` `T`s is laid out in the same way as a standard-layout struct containing `X` members, each of type `T`. – T.C. Aug 28 '14 at 08:06
  • 2
    @Praetorian Where is it stated that array and struct are layout-compatible? – Anton Savin Aug 28 '14 at 08:07
  • @AntonSavin Unless I'm misinterpreting, the layout compatible requirement then guarantees that all members of the union start at the same address. And T.C. may be right, I can't find anything more explicit than §3.9/9 (that I've quoted) that says the layout of the struct and array are compatible. I'm reading that as since an array of a standard layout type is also standard layout, it fulfills all the other requirements above. – Praetorian Aug 28 '14 at 08:15
  • 1
    @Praetorian §3.9/9 doesn't state that, it just defines what is standard-layout type. Moreover, §9.2/19 states that "There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment". And indeed, it is inserted in some cases, [see the code](http://coliru.stacked-crooked.com/a/4f6d8215969a058e) – Anton Savin Aug 28 '14 at 10:01
  • @AntonSavin I don't think the struct in your example is standard layout because of the alignment specifier. And the clause about padding being possible, but not at the beginning is why I'm confident all the union members will start at the same address. IMHO, the only fuzzy part of this is what TC said, whether a struct containing `N` `T`s is layout compatible with an array `T[N]`. – Praetorian Aug 28 '14 at 16:07
  • @Praetorian look at static asserts in the code - it's not only standard layout, it's POD. – Anton Savin Aug 28 '14 at 17:04
  • @AntonSavin (Sorry I missed this comment) I didn't notice the `static_assert`s, but it looks like this has [been discussed](https://stackoverflow.com/questions/21499966/common-initial-sequence-and-alignment) before, and the consensus is that it is a hole in the standard. Seems intuitive that if you add a special alignment requirement to one of the members you lose layout compatibility, unless you also add the same alignment spec to the other members too. – Praetorian Sep 04 '14 at 00:40
9

Anonymous unions are allowed in C++11/14. See the example of their usage at Bjarne Stroustrup's C++11 FAQ

Regarding anonymous structs see Why does C++11 not support anonymous structs, while C11 does? and Why does C++ disallow anonymous structs and unions?

Though most compilers support anonymous structs, if you want your code to be standard compliant you have to write something like this:

template <typename some_type>
struct vec
{
    union {
       struct { some_type x, y, z; } s1;
       struct { some_type r, g, b; } s2;

       some_type elements[3];
    };
};
Community
  • 1
  • 1
Anton Savin
  • 40,838
  • 8
  • 54
  • 90
8

I think the other answers sort of missed the point of the question:

I wish to access the members the same way as you would with this.

In other words, the question is really "how do I define a type vec in a standard-compliant manner such that given an object u of that type, u.x, u.r, and u.elements[0] all refer to the same thing?"

Well, if you insist on that syntax...then the obvious answer is: references.

So:

template <typename some_type>
struct vec
{
    vec() = default;
    vec(const vec& other) : elements{ other.elements[0], other.elements[1], other.elements[2] } {}

    vec & operator=(const vec &other) {
        elements[0] = other.elements[0];
        elements[1] = other.elements[1];
        elements[2] = other.elements[2];
        return *this;
    }

    some_type elements[3];
    some_type &x = elements[0], &y = elements[1], &z = elements[2];
    some_type &r = elements[0], &g = elements[1], &b = elements[2];    
};

The first problem with this approach is that you need extra space for 6 reference members - which is rather expensive for such a small struct.

The second problem with this approach is that given const vec<double> v;, v.x is still of type double &, so you could write v.x = 20; and have it compile without warning or error - only to get undefined behavior. Pretty bad.

So, in the alternative, you might consider using accessor functions:

template <typename some_type>
struct vec
{   
    some_type elements[3];
    some_type &x() { return elements[0]; }
    const some_type &x() const { return elements[0]; }

    some_type &y() { return elements[1]; }
    const some_type &y() const { return elements[1]; }

    some_type &z() { return elements[2]; }
    const some_type &z() const { return elements[2]; }

    some_type &r() { return elements[0]; }
    const some_type &r() const { return elements[0]; }

    some_type &g() { return elements[1]; }
    const some_type &g() const { return elements[1]; }

    some_type &b() { return elements[2]; }
    const some_type &b() const { return elements[2]; }
};

You would have to write u.x() etc. instead of u.x, but the space savings is considerable, you can also rely on the compiler-generated special member functions, it's trivially copyable if some_type is (which enables some optimizations), it's an aggregate and so can use the aggregate initialization syntax, and it's also const-correct.

Demo. Note that sizeof(vec<double>) is 72 for the first version and only 24 for the second.

T.C.
  • 133,968
  • 17
  • 288
  • 421