C++11 Anonymous Union with Non-Trivial Members

C++11 anonymous union with non-trivial members

There is no need for placement new here.

Variant members won't be initialized by the compiler-generated constructor, but there should be no trouble picking one and initializing it using the normal ctor-initializer-list. Members declared inside anonymous unions are actually members of the containing class, and can be initialized in the containing class's constructor.

This behavior is described in section 9.5. [class.union]:

A union-like class is a union or a class that has an anonymous union as a direct member. A union-like class X has a set of variant members. If X is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X.

and in section 12.6.2 [class.base.init]:

A ctor-initializer may initialize a variant member of the constructor’s class. If a ctor-initializer specifies more than one mem-initializer for the same member or for the same base class, the ctor-initializer is ill-formed.

So the code can be simply:

#include <new>

struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};

struct Foo
{
Foo() : p() {} // usual everyday initialization in the ctor-initializer
union {
int z;
double w;
Point p;
};
};

int main(void)
{
}

Of course, placement new should still be used when vivifying a variant member other than the other initialized in the constructor.

Why can't anonymous unions contain members with non-trivial constructors/destructors?

A bigger reason would be: how would the union know which destructor to call. The language itself doesn't track which member is active in a union.

It seems that C++0x will allow non-trivial types in unions, in which case you'll be forced to implement your own constructor(s) and destructor. (The latter is a little unclear from the proposal, it seems that the union destructor will not call any member destructors and the destructor for the right one would have to be invoked manually.)

Unnamed union member has non-trivial operator

Not sure if those modifications are possible for you, but following compiles

class Dummy
{
private:
double d;
public:
Dummy() = default;
Dummy(double d1): d{d1}{}
Dummy& operator = ( const Dummy& dm ) = default;
};

struct A{
bool isDummy = true;
union{
Dummy dm;
int i;

decltype(auto) operator=(double d) { dm = d; return *this; }
}B;

};

Demo

Problem with union containing a glm::vec2 (non-trivial default constructor)

At most one non-static data member of a union may have a brace-or-equal-initializer. [Note: if any non-static data member of a union has a non-trivial default constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be user-provided or it will be implicitly deleted (8.4.3) for the union. — end note ]

The relevant part is

if any non-static data member of a union has a non-trivial [member function] [...], [then] the corresponding member function of the union must be user-provided or it will be implicitly deleted.

So you will have to manually implement the constructor and destructor (and if you need them also the copy constructor, move constructor, copy assignment operator and move assignment operator).

The compiler can't provide a default constructor because there's no way it could know which of the union members should be initialized by the default constructor, or which union member the destructor needs to destruct.

So using the default constructor or trying to =default; it won't work, you'll have to provide user-specified implementations.


A basic implementation could look like this:

struct Foo
{
bool isVector;
union
{
std::vector<float> vec;
struct { float width, height; };
};

// by default we construct a vector in the union
Foo() : vec(), isVector(true) {

}

void setStruct(float width, float height) {
if(isVector) {
vec.~vector();
isVector = false;
}
this->width = width;
this->height = height;
}

void setVector(std::vector<float> vec) {
if(!isVector) {
new (&this->vec) std::vector<float>();
isVector = true;
}

this->vec = vec;
}

~Foo() {
if(isVector) vec.~vector();
}
};

godbolt example

Note that you need to manually manage the lifetime of the union members, since the compiler won't do that for you.

In case the active union member is the struct, but you want to activate the vector member, you need to use placement new to construct a new vector in the union.

The same is true in the opposite direction: if you want to switch the active union member from the vector to the struct, you need to call the vector destructor first.

We don't need to clean up the struct, since it's trivial, so no need to call it's constructor / destructor.


If possible i would recommend using std::variant or boost::variant instead, since those do exactly that: they keep track of the active union member and call the required constructors / destructors as needed.

Union member has a non-trivial copy constructor

You cannot.

A union combines two pieces of functionality: the ability to store an object which may be of a select number of types, and the ability to effectively (and implementation-defined) convert between those types. You could put an integer in and look at its representation as a double. And so forth.

Because a union must support both of these pieces of functionality (and for a few other reasons, like being able to construct one), a union prevents you from doing certain things. Namely, you cannot put "live" objects in them. Any object that is "living" enough that it needs a non-default copy constructor (among many other restrictions) cannot be a member of a union.

After all, a union object does not really have the concept of which type of data it actually stores. It does not store one type of data; it stores all of them, at the same time. It is up to you to be able to fish the right type out. So how could it reasonably copy one union value into another?

Members of a union must be a POD (plain-old-data) type. And while C++11 does loosen those rules, objects still must have a default (or otherwise trivial) copy constructor. And std::string's copy constructor is non-trivial.

What you likely want is a boost::variant. That is an object that can store a number of possible types, just like a union. Unlike a union however, it is type-safe. It therefore knows what is actually in the union; it is therefore able to copy itself and otherwise behave like a regular C++ object.

Initializing a union with a non-trivial constructor

Question 1: Default constructors do initialize POD members to 0 according to the C++ standard. See the quoted text below.

Question 2: If a constructor must be specified in a base class, then that class cannot be part of a union.

Finally, you can provide a constructor for your union:

union U 
{
A a;
B b;

U() { memset( this, 0, sizeof( U ) ); }
};

For Q1:

From C++03, 12.1 Constructors, pg 190

The implicitly-defined default constructor performs the set of initializations of the
class that would be performed by a user-written default constructor for that class with an empty mem-initializer-list (12.6.2) and an empty function body.

From C++03, 8.5 Initializers, pg 145

To default-initialize an object of type T means:

  • if T is a non-POD class type
    (clause 9), the default constructor
    for T is called (and the
    initialization is ill-formed if T
    has no accessible default
    constructor);
  • if T is an array type, each element is default-initialized;
  • otherwise, the object is zero-initialized.

To zero-initialize an object of type T means:

  • if T is a scalar type (3.9), the object is set to the value of 0 (zero) converted to T;
  • if T is a non-union class type, each non static data member and each base-class subobject is zero-initialized;
  • if T is a union type, the object’s first named data member is zero-initialized;
  • if T is an array type, each element is zero-initialized;
  • if T is a reference type, no initialization is performed.

For Q2:

From C++03, 12.1 Constructors, pg 190

A constructor is trivial if it is an implicitly-declared default constructor and if:

  • its class has no virtual functions (10.3) and no virtual base classes (10.1), and
  • all the direct base classes of its class have trivial constructors, and
  • for all the nonstatic data members of its class that are of class type (or array
    thereof), each such class has a trivial constructor

From C++03, 9.5 Unions, pg 162

A union can have member functions (including constructors and destructors), but not virtual (10.3) functions. A union shall not have base classes. A union shall not be used as a base class.An object of a class with a non-trivial constructor (12.1), a non-trivial copy constructor (12.8), a non-trivial destructor (12.4), or a non-trivial copy assignment operator (13.5.3, 12.8) cannot be a member of a union, nor can an array of such objects

C++ Default constructors in union with variant member with non-trivial default constructor

cppreference is generaly a nice information source, but here the compiler is right. Draft n3337 for C++11 contains in 9.5 Unions [class.union] a non normative but explicit note saying:

If any non-static data member of a union has a non-trivial default
constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move
assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be
user-provided or it will be implicitly deleted (8.4.3) for the union.

There is no mention that is will not be the case is one member has a default member initializer.

The same note still stands in draft n4296 for C++14 so I assume it should still be the same in the actual C++ standard. Notes are of course non normative, but their intent is to make more clear an interpretation of the standard, so I assume that the interpretation of cppreference is wrong because it does not respect that note, and that gcc interpretation is correct.



Related Topics



Leave a reply



Submit