Introduction

A union is a special class type that can hold only one of its non-static data members at a time. It's just big enough to accommodate the largest of its members.

9 [class]/5 A union is a class defined with the class-key union; it holds at most one data member at a time (9.5). [...]

9.5 [class.union]/1 In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time. [...] The size of a union is sufficient to contain the largest of its non-static data members. Each non-static data member is allocated as if it were the sole member of a struct. All non-static data members of a union object have the same address.

In C++98, members of a union where restricted to trivial object types. For these types, lifetime begins when appropriate storage is obtained, and ends when the storage is reused or released.

3.8 [basic.life]/1 [...] The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • if the object has non-trivial initialization, its initialization is complete.

The lifetime of an object of type T ends when:

  • if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
  • the storage which the object occupies is reused or released.

This special guarantee allows the active member of a union to be changed by simply assigning to it, effectively reusing the storage —that's at least the spirit, if not the letter, of the standard—.

Furthermore, a union does not know which member —if any— is active, so its special member functions have to be implemented without that information. Since members are restricted to trivial types, the special member functions can be implemented in terms of the underlying bytes, independently of the active member.

9.5 [class.union]/1 [...] 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. [...]

In C++11, this restriction was lifted; members of a union can now be of any object type. Switching between non-trivial members requires explicitly destroying the currently active member, and using placement new to construct the newly active member.

9.5 [class.union]/4 [Note: In general, one must use explicit destructor calls and placement new operators to change the active member of a union. —end note] [Example: Consider an object u of a union type U having non-static data members m of type M and n of type N. If M has a non-trivial destructor and N has a non-trivial constructor (for instance, if they declare or inherit virtual functions), the active member of u can be safely switched from m to n using the destructor and placement new operator as follows:

u.m.~M();
new (&u.n) N;

—end example]

If a special member function is non-trivial for any of the members, then the union special member function will be implicitly defined as deleted when not user-provided.

9.5 [class.union]/2 [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]

9.5 [class.union]/3 [Example: Consider the following union:

union U {
  int i;
  float f;
  std::string s;
};

Since std::string (21.3) declares non-trivial versions of all of the special member functions, U will have an implicitly deleted default constructor, copy/move constructor, copy/move assignment operator, and destructor. To use U, some or all of these member functions must be user-provided. -end example]

These non-trivial member functions can only be provided —with their usual semantics— by knowing if a member is active, and then forwarding to it. A discriminated union is a union or union-like class that is self-aware, that is, it contains some form of identifier that lets it know which member —if any— is active. A discriminated union can provide all special member functions, trivial or not.

An instance of eggs::variant<Ts...> is a discriminated union with members of object types Ts. It offers a natural interface for switching the active member, and it provides all special member functions with the usual semantics:

eggs::variants<N, M> u; // u has no active members
u = M{}; // u has an active member of type M
u = N{}; // u has an active member of type N, the previous active member was destroyed

// all special member functions are provided
using U = eggs::variants<int, float, std::string>;

Copyright Agustín Bergé, Fusion Fenix 2014-2017

Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)