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.

An anonymous union is a union definition of the form union { member-specification }; that lacks a class name and does not include a declarator list. It defines an unnamed type and implicitly creates an unnamed object of that type. Instead of creating a distinct namespace for its members, the C++ compiler injects the union’s data members directly into the enclosing scope (block, class, or namespace). All members of the anonymous union share the same memory location, and only one member can hold an active, valid value at any given time.
union {
    Type1 member1;
    Type2 member2;
}; // No union tag name, no declarator list

Scope Injection and Access

Because the members are injected into the enclosing scope, they are accessed as if they were standard variables declared directly in that scope. No intermediate union identifier is required. If the anonymous union is defined within a local block, its members are accessed directly. If it is defined within a struct or class, external access to its members still requires the standard dot (.) or arrow (->) operator on the parent instance, but the intermediate union name is omitted.
struct Entity {
    int type;
    
    // Anonymous union injected into the Entity class scope
    union {
        int integer_value;
        float float_value;
    };
};

int main() {
    Entity e;
    e.integer_value = 42;    // Accessed directly on the struct instance (no intermediate union name)
    e.float_value = 3.14f;   // Overwrites integer_value
    
    // Anonymous union injected into the local block scope
    union {
        char c;
        double d;
    };
    
    c = 'A';  // Accessed directly as a local variable
    d = 9.81; // Overwrites 'c'
    
    return 0;
}

Technical Constraints and Rules

The C++ standard imposes strict limitations on anonymous unions to prevent scope pollution and undefined behavior:
  1. No Declarators: The union must not have a tag name, and no declarator (such as an object name, pointer, or array) can be present after the closing brace.
  2. Public Members Only: An anonymous union cannot contain private or protected members. All members are implicitly and strictly public.
  3. No Member Functions: It cannot define member functions. This includes constructors, destructors, and overloaded operators.
  4. No Static Data: It cannot contain static data members.
  5. No Nested Types: An anonymous union cannot contain definitions for nested types. Defining a struct, class, or enum inside an anonymous union is prohibited.
  6. Namespace and Global Scope Linkage: If an anonymous union is declared at the namespace scope (global scope or within a named namespace), it must be explicitly declared with the static keyword to enforce internal linkage. Anonymous unions inside unnamed namespaces already possess internal linkage and do not require the static keyword.
// Global scope or named namespace anonymous union MUST be static
static union {
    int global_state;
    void* global_pointer;
};

namespace {
    // Unnamed namespace: static is not required
    union {
        int local_state;
        double local_value;
    };
}

C++11 Unrestricted Unions and Special Member Functions

Since C++11, unions (including anonymous unions) are permitted to contain members of types with non-trivial special member functions (e.g., types with custom constructors, destructors, or copy/move assignment operators, such as std::string). However, if an anonymous union inside a class or struct contains a member with a non-trivial special member function, the corresponding implicit special member function of the anonymous union is deleted. Consequently, the corresponding implicit special member function of the enclosing class or struct is also deleted. For example, if a union member has a non-trivial default constructor, the enclosing class’s implicit default constructor is deleted. If a member has a non-trivial copy constructor, the enclosing class’s implicit copy constructor is deleted. If the developer intends to instantiate, copy, move, or destroy the enclosing object, those deleted special member functions must be explicitly user-provided. Crucially, any constructor of the enclosing class must explicitly initialize at least one member of the anonymous union in its member initializer list; otherwise, the compiler will attempt to invoke the deleted default constructor of the anonymous union and fail. This initialized member can be either trivial or non-trivial.
#include <string>
#include <new> // Required for placement new

struct Wrapper {
    enum class Type { Int, String } active_member;
    
    union {
        int i;
        std::string s; // Non-trivial special member functions
    };

    // 1. Default Constructor
    // The implicit default constructor is deleted because 's' has a non-trivial default constructor.
    // We must explicitly initialize *some* member of the union in the initializer list.
    // Here, we initialize the non-trivial member 's'.
    Wrapper() : active_member(Type::String), s("default") {}
    
    // 2. Copy Constructor
    // The implicit copy constructor is deleted because 's' has a non-trivial copy constructor.
    // We must initialize *some* member in the initializer list to satisfy the union's 
    // initialization requirement before dynamically constructing the correct active member.
    // Here, initializing the trivial member 'i' acts as a safe placeholder.
    Wrapper(const Wrapper& other) : active_member(other.active_member), i(0) {
        if (active_member == Type::String) {
            // Placement new required to construct the non-trivial member,
            // safely changing the active member from 'i' to 's'.
            new (&s) std::string(other.s); 
        } else {
            i = other.i;
        }
    }

    // 3. Destructor
    // The implicit destructor is deleted because 's' has a non-trivial destructor.
    ~Wrapper() {
        if (active_member == Type::String) {
            // Explicit standard destructor call required. 
            // Note: Because std::string is a typedef for std::basic_string<char> inside 
            // the std namespace, ordinary name lookup for `~string()` fails without 
            // explicit qualification. You must use the injected-class-name (~basic_string()) 
            // or fully qualify the call (s.std::string::~string()).
            s.~basic_string(); 
        }
    }

    // Note: To fully support copying and moving, the copy assignment, 
    // move constructor, and move assignment operators must also be explicitly 
    // defined following similar active-member checking logic.
};

Memory Layout and Alignment

The memory footprint of an anonymous union is determined by its largest data member, plus any padding required to satisfy the alignment requirements of that member. The enclosing scope allocates this memory block once, and all identifiers within the anonymous union alias the base address of this block. If an anonymous union is placed inside a struct or class, it contributes to the overall size and alignment of the parent object exactly as a named union would, but without requiring an intermediate identifier to access the memory.
Master C++ with Deep Grasping Methodology!Learn More