You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++20中用模板实现含可变数量类型的union

在C++20中实现带可变数量类型的Union结构体

首先明确:C++没有直接语法支持为模板参数包中的每个类型自动生成val1、val2这类带编号的成员标识符(因为标识符必须在编译期提前确定,无法随参数包动态生成)。不过我们可以通过两种方式实现类似需求:

1. 手动管理内存的自定义可变类型存储(模拟Union行为)

这种方式完全手动控制内存和对象生命周期,和原生Union的行为一致,同时支持任意数量的类型:

#include <cstddef>
#include <utility>
#include <type_traits>
#include <algorithm>

template <typename... Args>
struct Foo {
    // 分配足够容纳最大类型的内存,对齐到所有类型的最大对齐要求
    alignas(Args...) unsigned char storage[std::max({sizeof(Args)...})];
    // 存储当前活跃类型的索引,用于析构和访问
    std::size_t active_index;

    // 构造函数:根据传入的类型初始化内存
    template <typename T>
    Foo(T&& val) : active_index([&]() {
        // 找到当前类型在参数包中的索引
        std::size_t idx = 0;
        ((std::is_same_v<T, Args> ? true : (idx++, false)) || ...);
        return idx;
    }()) {
        static_assert((std::is_same_v<T, Args> || ...), "类型不在模板参数列表中");
        // 定位new构造对象
        new (storage) T(std::forward<T>(val));
    }

    // 析构函数:根据活跃索引调用对应类型的析构函数
    ~Foo() {
        auto destroy = [this]<std::size_t... Is>(std::index_sequence<Is...>) {
            ((Is == active_index ? (std::launder(reinterpret_cast<Args*>(storage))->~Args(), true) : false) || ...);
        };
        destroy(std::index_sequence_for<Args...>{});
    }

    // 禁止拷贝和移动,如需支持需手动实现(处理对象生命周期)
    Foo(const Foo&) = delete;
    Foo& operator=(const Foo&) = delete;
    Foo(Foo&&) = delete;
    Foo& operator=(Foo&&) = delete;

    // 根据类型获取引用(需确保当前活跃类型匹配,否则行为未定义)
    template <typename T>
    T& get() {
        static_assert((std::is_same_v<T, Args> || ...), "类型不在模板参数列表中");
        return *std::launder(reinterpret_cast<T*>(storage));
    }

    // 根据索引获取引用(索引需在0~sizeof...(Args)-1范围内)
    template <std::size_t I>
    auto& get() {
        using T = std::tuple_element_t<I, std::tuple<Args...>>;
        return *std::launder(reinterpret_cast<T*>(storage));
    }
};

// 使用示例
int main() {
    Foo<int, std::string, double> foo(42);
    foo.get<int>() = 100;
    // 切换类型:注意需手动析构当前对象
    foo.get<int>().~int();
    new (foo.storage) std::string("hello");
    foo.active_index = 1;
    return 0;
}

2. 使用标准库std::variant(推荐)

如果不需要手动管理内存和生命周期,C++17起提供的std::variant就是现成的解决方案,它底层基于Union实现,支持任意数量的类型,并且自动处理对象的构造、析构和类型安全访问:

#include <variant>
#include <string>
#include <iostream>

// 直接用std::variant作为你的Foo模板
template <typename... Args>
using Foo = std::variant<Args...>;

int main() {
    Foo<int, std::string, double> foo;
    // 存储int类型
    foo = 42;
    std::cout << std::get<int>(foo) << std::endl;
    // 切换存储string类型
    foo = "hello world";
    std::cout << std::get<std::string>(foo) << std::endl;
    // 使用std::visit访问任意类型
    std::visit([](auto&& val) {
        std::cout << "当前值:" << val << std::endl;
    }, foo);
    return 0;
}

为什么无法直接生成val1、val2这类成员?

C++的标识符(比如变量名)必须在编译阶段提前确定,而模板参数包的大小和类型是在实例化时才确定的,没有语法支持动态生成带编号的标识符。如果硬要类似的命名,只能针对固定数量的类型手动重载模板,但这失去了可变参数的灵活性。

内容的提问来源于stack exchange,提问作者FEDOS-o

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 02:50:28