Anonymous union and struct [duplicate]

2020-03-08 09:32发布

问题:

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

回答1:

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).



回答2:

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


回答3:

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.