Union declaration

From cppreference.com
< cpp‎ | language
 
 
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements
Jump statements
Functions
function declaration
lambda function declaration
function template
inline specifier
exception specifications (deprecated)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
union types
function types
decltype specifier (C++11)
Specifiers
cv specifiers
storage duration specifiers
constexpr specifier (C++11)
auto specifier (C++11)
alignas specifier (C++11)
Initialization
Literals
Expressions
alternative representations
Utilities
Types
typedef declaration
type alias declaration (C++11)
attributes (C++11)
Casts
implicit conversions
const_cast conversion
static_cast conversion
dynamic_cast conversion
reinterpret_cast conversion
C-style and functional cast
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
class template
function template
template specialization
parameter packs (C++11)
Miscellaneous
Inline assembly
 

A union is a special class type that can hold only one of its non-static data members at a time.

The class specifier for a union declaration is similar to class or struct declaration:

union attr class-head-name { member-specification }
attr(C++11) - optional sequence of any number of attributes
class-head-name - the name of the union that's being defined. Optionally prepended by nested-name-specifier (sequence of names and scope-resolution operators, ending with scope-resolution operator). The name may be omitted, in which case the union is unnamed
member-specification - list of access specifiers, member object and member function declarations and definitions.

A union can have member functions (including constructors and destructors), but not virtual functions.

A union cannot have base classes and cannot be used as a base class.

A union cannot have data members of reference types.

Unions cannot contain a non-static data member with a non-trivial special member function (copy constructor, copy-assignment operator, or destructor). (until C++11)
If a union contains a non-static data member with a non-trivial special member function (copy/move constructor, copy/move assignment, or destructor) that function is deleted by default in the union and needs to be defined explicitly by the programmer.

At most one data member can have a brace-or-equals initializer

(since C++11)

Just like in struct declaration, the default member access in a union is public:.

[edit] Explanation

The union is only as big as necessary to hold its largest data member. The other data members are allocated in the same bytes as part of that largest member. The details of that allocation are implementation-defined, and it's undefined behavior to read from the member of the union that wasn't most recently written as it violates type aliasing. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.

#include <iostream>
union S {
    std::int32_t n; // occupies 4 bytes
    std::uint16_t s[2]; // occupies 4 bytes
    std::uint8_t c; // occupies 1 byte
}; // the whole union occupies 4 bytes
 
int main()
{
    S s = {0x12345678}; // initalizes the first member, s.n is now the active member
    // at this point, reading from s.s or s.c is UB
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s is now the active member
    // at this point, reading from n or c is UB but most compilers define this
    std::cout << "s.c is now " << +s.c << '\n' // 11 or 00, depending on platform
              << "s.n is now " << s.n << '\n'; // 12340011 or 00115678 
}

Each member is allocated as if it is the only member of the class, which is why s.c in the example above aliases the first byte of s.s[0].

If members of a union are classes with user-defined constructors and destructors, to switch the active member, explicit destructor and placement new are generally needed:

#include <iostream>
#include <string>
#include <vector>
union S {
    std::string str;
    std::vector<int> vec;
    ~S() {} // needs to know which member is active, only possible in union-like class 
}; // the whole union occupies max(sizeof(string), sizeof(vector<int>))
 
int main()
{
    S s = {"Hello, world"};
    // at this point, reading from s.vec is UB
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string<char>();
    new (&s.vec) std::vector<int>;
    // now, s.vec is the active member of the union
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector<int>();
}
(since C++11)

If two union members are standard-layout types, it's well-defined to examine their common subsequence on any compiler.

[edit] Anonymous unions

An unnamed union definition that does not define any objects, pointers, or references is an anonymous union definition.

union { member-specification } ;

Anonymous unions have further restrictions: they cannot have member functions, cannot have static data members, and all their non-static data members must be public.

Members of an anonymous union are injected in the enclosing scope (and must not conflict with other names declared there).

int main()
{
    union { int a; const char* p; };
    a = 1;
    p = "Jennifer";
}

Namespace-scope anonymous unions must be static.

[edit] Union-like classes

A union-like class is any class with at least one anonymous union as a member. The members of that anonymous union are called variant members. Union-like classes can be used to implement tagged unions.

#include <iostream>
// S has one non-static data member (tag), three enumerator members, 
// and three variant members (c, n, d)
struct S
{
    enum {CHAR, INT, DOUBLE} tag;
    union {
        char c;
        int n;
        double d;
    };
};
 
void print_s(const S& s)
{
    switch(s.tag)
    {
        case S::CHAR: std::cout << s.c << '\n'; break;
        case S::INT: std::cout << s.n << '\n'; break;
        case S::DOUBLE: std::cout << s.d << '\n'; break;
    }
}
int main()
{
    S s = {S::CHAR, 'a'};
    print_s(s);
    s.tag = S::INT; s.n = 123;
    print_s(s);
}