Chapter 26:Discriminated Unions_《C++ Templates》notes

Discriminated Unions

      • [1. Key Concepts & Implementation](#1. Key Concepts & Implementation)
      • [2. Visitor Pattern Implementation](#2. Visitor Pattern Implementation)
      • [3. Copy/Move Semantics](#3. Copy/Move Semantics)
      • [4. Exception Safety](#4. Exception Safety)
      • [Multiple-Choice Questions](#Multiple-Choice Questions)
      • [Design Questions](#Design Questions)
      • [Code Testing](#Code Testing)
      • [5. Key Challenges](#5. Key Challenges)

1. Key Concepts & Implementation

1.1 Storage and Type Discriminator

A discriminated union needs:

  • Type-safe storage for multiple types
  • A discriminator tag to track active type
cpp 复制代码
#include <type_traits>
#include <stdexcept>

template<typename... Types>
class Variant {
    enum class TypeTag { None, Int, Double, String };
    TypeTag tag = TypeTag::None;

    static constexpr size_t BufferSize = 
        std::max({sizeof(int), sizeof(double), sizeof(std::string)});
    alignas(std::max_align_t) char buffer[BufferSize];

    template<typename T> void destroy() {
        if constexpr (!std::is_trivially_destructible_v<T>) {
            reinterpret_cast<T*>(buffer)->~T();
        }
    }

public:
    // Default constructor
    Variant() = default;

    // Constructor for supported types
    template<typename T>
    Variant(T&& value) {
        emplace<std::decay_t<T>>(std::forward<T>(value));
    }

    ~Variant() { reset(); }

    template<typename T, typename... Args>
    void emplace(Args&&... args) {
        reset();
        new(buffer) T(std::forward<Args>(args)...);
        if constexpr (std::is_same_v<T, int>) tag = TypeTag::Int;
        else if constexpr (std::is_same_v<T, double>) tag = TypeTag::Double;
        else if constexpr (std::is_same_v<T, std::string>) tag = TypeTag::String;
    }

    void reset() {
        switch(tag) {
            case TypeTag::Int: destroy<int>(); break;
            case TypeTag::Double: destroy<double>(); break;
            case TypeTag::String: destroy<std::string>(); break;
            default: break;
        }
        tag = TypeTag::None;
    }

    // Type checkers
    bool is_int() const { return tag == TypeTag::Int; }
    bool is_double() const { return tag == TypeTag::Double; }
    bool is_string() const { return tag == TypeTag::String; }

    // Value accessors
    int& get_int() { 
        if (tag != TypeTag::Int) throw std::bad_variant_access{};
        return *reinterpret_cast<int*>(buffer);
    }
    
    // Similar get_double(), get_string()...
};

Test Case:

cpp 复制代码
int main() {
    Variant<int, double, std::string> v;
    v.emplace<int>(42);
    std::cout << (v.is_int() ? "Int: " + std::to_string(v.get_int()) : "Not int") << "\n"; // Int: 42
    
    v.emplace<std::string>("Hello");
    std::cout << (v.is_string() ? "String: " + v.get_string() : "Not string") << "\n"; // String: Hello
    
    try { v.get_double(); } 
    catch(const std::exception& e) { std::cout << e.what() << "\n"; } // Throws
}

2. Visitor Pattern Implementation

Use overloaded functors or generic lambdas to handle different types.

cpp 复制代码
template<typename... Fs>
struct Overload : Fs... { using Fs::operator()...; };

template<typename... Fs> Overload(Fs...) -> Overload<Fs...>;

template<typename Variant, typename... Visitors>
decltype(auto) visit(Variant&& var, Visitors&&... vis) {
    auto&& visitor = Overload{std::forward<Visitors>(vis)...};
    if (var.is_int()) return visitor(var.get_int());
    else if (var.is_double()) return visitor(var.get_double());
    else if (var.is_string()) return visitor(var.get_string());
    throw std::bad_variant_access{};
}

Test Case:

cpp 复制代码
int main() {
    Variant<int, double, std::string> v = 3.14;
    
    visit(v, 
        [](int i) { std::cout << "Int: " << i; },
        [](double d) { std::cout << "Double: " << d; },
        [](const std::string& s) { std::cout << "String: " << s; }
    ); // Double: 3.14
}

3. Copy/Move Semantics

Implement proper copy/move constructors and assignment operators.

cpp 复制代码
Variant(const Variant& other) {
    switch(other.tag) {
        case TypeTag::Int: emplace<int>(other.get_int()); break;
        case TypeTag::Double: emplace<double>(other.get_double()); break;
        case TypeTag::String: emplace<std::string>(other.get_string()); break;
        default: break;
    }
}

Variant& operator=(const Variant& other) {
    if (this != &other) {
        reset();
        // ... same as copy constructor ...
    }
    return *this;
}

// Similar for move operations...

Test Case:

cpp 复制代码
int main() {
    Variant<int, double, std::string> v1 = "Test";
    auto v2 = v1; // Copy constructor
    std::cout << (v2.is_string() ? v2.get_string() : "") << "\n"; // Test
    
    Variant v3 = std::move(v1); // Move constructor
    std::cout << (v3.is_string() ? v3.get_string() : "") << "\n"; // Test
    std::cout << (v1.is_string() ? "v1 still has value" : "v1 empty") << "\n"; // v1 empty
}

4. Exception Safety

Ensure exception safety during assignment/emplacement.

cpp 复制代码
template<typename T, typename... Args>
void emplace(Args&&... args) {
    reset();
    try {
        new(buffer) T(std::forward<Args>(args)...);
        // Update tag...
    } catch(...) {
        tag = TypeTag::None; // Rollback state
        throw;
    }
}

Multiple-Choice Questions

Question 1
Which techniques are valid for implementing storage in a discriminated union (variant)?

A. Using void* and dynamic allocation

B. Leveraging std::aligned_storage with a type tag

C. Storing raw bytes in a char array with alignment

D. Using a union of all possible types

E. Allocating memory via new for each type

Correct Answers: B, C, D
Explanation:

  • std::aligned_storage (B) ensures proper alignment. A raw char array © with manual alignment management is also valid. A union (D) directly holds all types but requires explicit tagging.
  • void* (A) and new (E) introduce unnecessary overhead and violate RAII principles.

Question 2
What is the purpose of a "type tag" in a discriminated union?

A. To track the index of the active type

B. To enable RTTI (Run-Time Type Information)

C. To avoid undefined behavior during destruction

D. To optimize alignment calculations

E. To validate assignments at compile-time

Correct Answers: A, C
Explanation: The type tag (A) identifies the active type to ensure the correct destructor © is called. It doesn't use RTTI (B) or compile-time checks (E).


Question 3
Which operations must a discriminated union handle explicitly?

A. Copy construction

B. Move assignment

C. Destruction based on the active type

D. Compile-time type checks

E. Dynamic memory reallocation

Correct Answers: A, B, C
Explanation: Copy/move operations (A, B) and destruction © depend on the active type. D is handled via templates, and E is unnecessary with RAII.


Question 4
How does the visitor pattern apply to a discriminated union?

A. By using virtual functions for each type

B. Via std::visit with overloaded function objects

C. Through template specialization for each type

D. By storing a function pointer

E. Using CRTP (Curiously Recurring Template Pattern)

Correct Answers: B, C
Explanation: std::visit (B) and template specializations © are common. Virtual functions (A) and CRTP (E) are not directly applicable.


Question 5
Which C++17 features simplify variant implementations?

A. if constexpr

B. std::variant

C. Fold expressions

D. Structured bindings

E. Class template argument deduction (CTAD)

Correct Answers: A, B
Explanation: if constexpr (A) simplifies type checks. std::variant (B) is a standard implementation. Others are unrelated.


Question 6
What is essential for exception-safe variant assignment?

A. Using the copy-and-swap idiom

B. Destroying the old object before assignment

C. noexcept move operations

D. Allocating memory before assignment

E. Type traits for trivial destructibility

Correct Answers: A, C
Explanation: Copy-and-swap (A) and noexcept moves © ensure exception safety. Premature destruction (B) risks leaks.


Question 7
Which type traits are critical for variant storage management?

A. std::is_trivially_copyable

B. std::is_empty

C. std::is_nothrow_move_constructible

D. std::is_same

E. std::is_abstract

Correct Answers: A, C
Explanation: Trivial copy (A) and noexcept move © traits optimize storage. Others are irrelevant.


Question 8
How to prevent undefined behavior when accessing a variant?

A. Runtime type checks with std::get_if

B. Compile-time assertions

C. Throwing std::bad_variant_access

D. Using std::visit for all accesses

E. Tag-based conditional logic

Correct Answers: A, C, D
Explanation: Runtime checks (A), exceptions ©, and std::visit (D) ensure safe access. Compile-time checks (B) are insufficient for dynamic types.


Question 9
What is the role of std::launder in variant implementations?

A. To avoid pointer optimization issues

B. To enable constexpr context storage

C. To manage memory alignment

D. To support trivially destructible types

E. To prevent memory leaks

Correct Answers: A
Explanation: std::launder (A) ensures correct pointer semantics. Others are unrelated.


Question 10
Which optimizations are possible for variants with trivially destructible types?

A. Skipping destructor calls

B. Using memcpy for copies

C. Storing types in a union without a tag

D. Avoiding move constructors

E. Inlining accessor functions

Correct Answers: A, B
Explanation: Trivial types allow skipping destructors (A) and using memcpy (B). Others are incorrect.


Design Questions

Question 1
Implement a simplified Variant class supporting int, double, and std::string with type-safe access.

Solution:

cpp 复制代码
#include <string>
#include <stdexcept>
#include <type_traits>

template<typename... Types>
class Variant {
    using LargestType = std::aligned_union_t<0, Types...>;
    LargestType storage;
    size_t typeIndex;

public:
    template<typename T>
    Variant(T&& val) : typeIndex(sizeof...(Types)) {
        static_assert((std::is_same_v<std::decay_t<T>, Types> || ...), "Invalid type");
        new(&storage) std::decay_t<T>(std::forward<T>(val));
        typeIndex = index_of<std::decay_t<T>>();
    }

    ~Variant() {
        // Call destructor based on typeIndex (omitted for brevity)
    }

    template<typename T>
    T& get() {
        if (typeIndex != index_of<T>()) throw std::bad_variant_access{};
        return *reinterpret_cast<T*>(&storage);
    }

private:
    template<typename T>
    static constexpr size_t index_of() {
        size_t index = 0;
        ((std::is_same_v<T, Types> ? false : (++index, true)) && ...);
        return index;
    }
};

// Test
int main() {
    Variant<int, double, std::string> v(42);
    assert(v.get<int>() == 42);
}

Question 2
Design a visitor mechanism for the Variant class using std::visit-like syntax.

Solution:

cpp 复制代码
template<typename... Fs>
struct Overload : Fs... { using Fs::operator()...; };

template<typename... Fs> Overload(Fs...) -> Overload<Fs...>;

template<typename Variant, typename... Visitors>
auto visit(Variant&& v, Visitors&&... vis) {
    auto&& overload = Overload{std::forward<Visitors>(vis)...};
    switch (v.type_index()) {
        case 0: return overload(v.template get<0>());
        // ... handle other cases
    }
}

// Test
int main() {
    Variant<int, std::string> v("hello");
    visit(v, [](int i) { /* ... */ }, [](const std::string& s) { assert(s == "hello"); });
}

Question 3
Implement move semantics for the Variant class, ensuring exception safety.

Solution:

cpp 复制代码
Variant(Variant&& other) noexcept((std::is_nothrow_move_constructible_v<Types> && ...)) {
    // Move each possible type with noexcept check
}

Variant& operator=(Variant&& other) {
    if (this != &other) {
        this->~Variant();
        new(this) Variant(std::move(other));
    }
    return *this;
}

// Test
int main() {
    Variant<std::string> v1("test");
    auto v2 = std::move(v1);
    assert(v2.get<std::string>() == "test");
}

Question 4
Add a emplace method to construct a type in-place within the Variant.

Solution:

cpp 复制代码
template<typename T, typename... Args>
void emplace(Args&&... args) {
    static_assert((std::is_same_v<T, Types> || ...), "Invalid type");
    this->~Variant();
    new(&storage) T(std::forward<Args>(args)...);
    typeIndex = index_of<T>();
}

// Test
int main() {
    Variant<std::string> v;
    v.emplace<std::string>(3, 'a');
    assert(v.get<std::string>() == "aaa");
}

Question 5
Implement a Variant copy constructor handling non-trivially copyable types.

Solution:

cpp 复制代码
Variant(const Variant& other) : typeIndex(other.typeIndex) {
    // Use switch-case to copy-construct based on typeIndex
    switch (typeIndex) {
        case 0: new(&storage) std::decay_t<decltype(other.get<0>())>(other.get<0>()); break;
        // ... other cases
    }
}

// Test
int main() {
    Variant<std::string> v1("copy");
    Variant<std::string> v2(v1);
    assert(v2.get<std::string>() == "copy");
}

Code Testing

Each code snippet includes a main function with test cases. Compile with C++17 or later:

bash 复制代码
g++ -std=c++17 -o test test.cpp && ./test

Ensure all assertions pass and valgrind reports no memory leaks.

5. Key Challenges

  1. Memory Alignment : Use alignas and properly sized buffer.
  2. Type-Safe Access: Runtime type checking before value access.
  3. Generic Visitors: Leverage C++17's class template argument deduction for overload sets.
  4. Move Semantics: Properly handle moved-from states.

Compile all examples with:

bash 复制代码
g++ -std=c++17 variant_example.cpp -o variant_example
相关推荐
DKPT39 分钟前
Java设计模式之行为型模式(责任链模式)介绍与说明
java·笔记·学习·观察者模式·设计模式
L_autinue_Star1 小时前
手写vector容器:C++模板实战指南(从0到1掌握泛型编程)
java·c语言·开发语言·c++·学习·stl
崔高杰1 小时前
微调性能赶不上提示工程怎么办?Can Gradient Descent Simulate Prompting?——论文阅读笔记
论文阅读·人工智能·笔记·语言模型
元气小嘉1 小时前
前端技术小结
开发语言·前端·javascript·vue.js·人工智能
励志的大鹰哥1 小时前
V少JS基础班之第七弹
开发语言·javascript·ecmascript
KhalilRuan2 小时前
Unity Demo——3D平台跳跃游戏笔记
笔记·游戏·unity·游戏引擎
AI360labs_atyun2 小时前
Java在AI时代的演进与应用:一个务实的视角
java·开发语言·人工智能·科技·学习·ai
凤年徐3 小时前
【数据结构与算法】203.移除链表元素(LeetCode)图文详解
c语言·开发语言·数据结构·算法·leetcode·链表·刷题
nbsaas-boot3 小时前
多租户架构下的多线程处理实践指南
java·开发语言·spring
无小道3 小时前
c++--typedef和#define的用法及区别
c语言·开发语言·汇编·c++