CodeNebula
C++11coreintermediate

Unrestricted Unions (C++11 Addition)

C++11 · Modern Revolution · Compiler flag: -std=c++11

Explanation

Unions can now contain members with non-trivial constructors, destructors, and copy/move operations, though the programmer must manually manage construction and destruction of such members.

Real-World Scenario

Implement a variant-like type that can hold either a string or a numeric value in a memory-efficient tagged union.

Quick Snippet

snippet.cpp
union Variant {
    int num;
    std::string str;
    Variant() : num(0) {}
    ~Variant() {} // must manually manage non-trivial members
};

Full Example

unrestricted_unions.cpp
// CodeNebula - C++11 Example: Unrestricted Unions
// Compile: g++ -std=c++11 -Wall -Wextra -o example unrestricted_unions.cpp
//
// This example demonstrates:
// - C++11 unions can contain members with non-trivial constructors/destructors
// - Manual lifetime management of union members using placement new
// - Tagged union (discriminated union) pattern
// - Comparison with C++98 union restrictions

#include <iostream>
#include <string>
#include <new>       // placement new
#include <cstdint>

// ============================================================
// 1. In C++98, unions could only hold POD types.
//    In C++11, unions can hold types like std::string.
// ============================================================
union SimpleUnion {
    int i;
    double d;
    std::string s;  // Would be illegal in C++98!

    // Must provide constructor/destructor since string is non-trivial
    SimpleUnion() : i(0) {}
    ~SimpleUnion() {}  // Does NOT destroy s automatically
};

// ============================================================
// 2. Tagged union (discriminated union) pattern
// ============================================================
class Variant {
public:
    enum Type { INT, DOUBLE, STRING, NONE };

private:
    Type type_;
    union Storage {
        int i;
        double d;
        std::string s;

        Storage() {}   // No-op: active member set manually
        ~Storage() {}  // No-op: Variant manages lifetime
    } data_;

    void destroy() {
        if (type_ == STRING) {
            data_.s.~basic_string();  // Explicit destructor call
        }
        type_ = NONE;
    }

public:
    Variant() : type_(NONE) {}

    Variant(int val) : type_(INT) {
        data_.i = val;
    }

    Variant(double val) : type_(DOUBLE) {
        data_.d = val;
    }

    Variant(const std::string& val) : type_(STRING) {
        new (&data_.s) std::string(val);  // Placement new
    }

    ~Variant() { destroy(); }

    // Copy constructor
    Variant(const Variant& other) : type_(NONE) {
        switch (other.type_) {
            case INT:    type_ = INT;    data_.i = other.data_.i; break;
            case DOUBLE: type_ = DOUBLE; data_.d = other.data_.d; break;
            case STRING: type_ = STRING; new (&data_.s) std::string(other.data_.s); break;
            case NONE:   break;
        }
    }

    // Assignment
    Variant& operator=(const Variant& other) {
        if (this != &other) {
            destroy();
            switch (other.type_) {
                case INT:    type_ = INT;    data_.i = other.data_.i; break;
                case DOUBLE: type_ = DOUBLE; data_.d = other.data_.d; break;
                case STRING: type_ = STRING; new (&data_.s) std::string(other.data_.s); break;
                case NONE:   break;
            }
        }
        return *this;
    }

    Type getType() const { return type_; }

    void print() const {
        switch (type_) {
            case INT:    std::cout << "int(" << data_.i << ")"; break;
            case DOUBLE: std::cout << "double(" << data_.d << ")"; break;
            case STRING: std::cout << "string(\"" << data_.s << "\")"; break;
            case NONE:   std::cout << "none"; break;
        }
    }
};

int main() {
    std::cout << "=== Unrestricted Unions ===" << std::endl;

    // Basic union with string
    std::cout << "\n--- Simple union with std::string ---" << std::endl;
    SimpleUnion su;
    su.i = 42;
    std::cout << "  As int: " << su.i << std::endl;

    new (&su.s) std::string("Hello, union!");
    std::cout << "  As string: " << su.s << std::endl;
    su.s.~basic_string();  // Must destroy manually

    // Tagged variant
    std::cout << "\n--- Tagged variant ---" << std::endl;
    Variant v1(42);
    Variant v2(3.14);
    Variant v3(std::string("C++11"));
    Variant v4;

    std::cout << "  v1 = "; v1.print(); std::cout << std::endl;
    std::cout << "  v2 = "; v2.print(); std::cout << std::endl;
    std::cout << "  v3 = "; v3.print(); std::cout << std::endl;
    std::cout << "  v4 = "; v4.print(); std::cout << std::endl;

    // Reassignment changes type
    std::cout << "\n--- Reassignment ---" << std::endl;
    v1 = v3;  // int -> string
    std::cout << "  v1 after = v3: "; v1.print(); std::cout << std::endl;

    v2 = Variant(99);
    std::cout << "  v2 after = 99: "; v2.print(); std::cout << std::endl;

    std::cout << "\n  sizeof(Variant): " << sizeof(Variant) << " bytes" << std::endl;

    return 0;
}

// Expected output:
// === Unrestricted Unions ===
//
// --- Simple union with std::string ---
//   As int: 42
//   As string: Hello, union!
//
// --- Tagged variant ---
//   v1 = int(42)
//   v2 = double(3.14)
//   v3 = string("C++11")
//   v4 = none
//
// --- Reassignment ---
//   v1 after = v3: string("C++11")
//   v2 after = 99: int(99)
//
//   sizeof(Variant): ... bytes
Run command
g++ -std=c++11 -Wall -Wextra -o example unrestricted_unions.cpp