Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A union is a user-defined data type in which all members share the same memory location. Unlike a struct or class where each member has its own distinct memory address, a union allocates only enough memory to hold its largest member, and its overall alignment is determined by the member with the strictest alignment requirement. Consequently, a union can store a valid value for only one of its non-static data members at any given time.
union UnionName {
    type1 member1;
    type2 member2;
    type3 member3;
};

Memory Layout

The sizeof a union is equal to the size of its largest data member, potentially padded to satisfy the alignment requirements of the overall union.
union Data {
    char c;      // 1 byte
    int i;       // 4 bytes
    double d;    // 8 bytes
};
// sizeof(Data) is 8 bytes.
// All members start at the exact same memory address.

The Active Member and Undefined Behavior

In C++, the member that was most recently written to is known as the active member. Reading from a member other than the active member results in Undefined Behavior (UB). C++ strictly forbids using unions for type punning (interpreting the bit representation of one type as another type), which is a notable divergence from C.
Data data;
data.i = 42;          // 'i' becomes the active member
int val = data.i;     // Valid: reading the active member

// double bad = data.d; // UNDEFINED BEHAVIOR: 'd' is not the active member

Anonymous Unions

An anonymous union is a union defined without a type name and without declaring an object of that type. The members of an anonymous union are injected directly into the enclosing scope and are accessed as if they were direct variables of that scope. If an anonymous union is declared at namespace scope (global scope), it must be explicitly declared static.
static union {        // Namespace scope anonymous union must be static
    int global_int;
    float global_float;
};

struct Token {
    int token_type;
    union {           // Anonymous union at class scope
        int int_val;
        float float_val;
    };
};

Token t;
t.token_type = 1;
t.int_val = 100;      // Accessed directly, no union variable name required

Unrestricted Unions (C++11)

Prior to C++11, unions were restricted to Plain Old Data (POD) types. They could not contain members with non-trivial constructors, destructors, copy constructors, or assignment operators (e.g., std::string or std::vector). C++11 introduced unrestricted unions, allowing members with non-trivial special member functions. However, if a union contains a non-trivial member, the compiler implicitly deletes the union’s corresponding default special member functions (constructor, destructor, etc.). The developer assumes full responsibility for managing the object lifecycle using placement new to construct the active member and explicit destructor calls to destroy it.
#include <string>
#include <new>

union UnrestrictedData {
    int i;
    std::string s; // Non-trivial member

    // Default constructor and destructor must be explicitly defined
    // because the inclusion of std::string causes them to be implicitly deleted.
    UnrestrictedData() {} 
    ~UnrestrictedData() {} 
};

int main() {
    UnrestrictedData ud;
    
    // Constructing the non-trivial member requires placement new
    new (&ud.s) std::string("Hello World"); 
    
    // Destroying the non-trivial member requires an explicit destructor call
    using std::string;
    ud.s.~string(); 
    
    // Switching the active member to a trivial type
    ud.i = 42; 
    
    return 0;
}

std::variant (C++17)

Because managing the lifecycle of non-trivial members in unrestricted unions is highly error-prone, C++17 introduced std::variant. It is the standard, type-safe alternative to raw unions. std::variant automatically manages the construction and destruction of its active member, tracks which type is currently active, and prevents undefined behavior by throwing an exception (std::bad_variant_access) on invalid accesses.
#include <variant>
#include <string>

// std::variant automatically handles memory and lifecycle management
std::variant<int, std::string> safe_data;

safe_data = "Hello World"; // Safely constructs std::string
safe_data = 42;            // Safely destroys std::string, constructs int

Access Control and Member Functions

Like classes and structs, unions can have access specifiers (public, private, protected), member functions, constructors, and destructors. By default, all members of a union are public. However, unions have several fundamental restrictions:
  • They cannot contain reference members.
  • They cannot inherit from other classes.
  • They cannot be used as base classes.
  • They cannot contain virtual functions.
Master C++ with Deep Grasping Methodology!Learn More