文档目录

解决什么问题?

// ❌ 传统 union 的问题
union Value {
    int i;
    double d;
    const char* s;
};

Value v;
v.i = 42;        // 现在 v 存的是 int
v.d = 3.14;      // 现在 v 存的是 double,覆盖了 int
// 编译器不管!v.i 现在是垃圾数据,但代码能访问 v.i → 未定义行为!
// 你必须自己用 tag 记录当前类型,但很容易忘记

variant 的承诺:编译期记住当前是哪种类型,访问错了会报错/抛异常。

什么时候用?

场景 为什么选 variant
函数返回"要么是正常结果,要么是错误信息" 替代 pair<bool, T> 或 -1 这样的哨兵值
解析器(JSON、INI、YAML)的节点类型 JSON 值可以是 string/number/array/object
状态机 状态是多种不同的结构体,但同一时刻只有一种
返回异构类型 函数可能返回 int、double 或 string

三种访问方式(选哪种?)

std::variant<int, double, std::string> v = 42;

// 方式 1:get<T>——你知道当前是什么类型时用
int val = std::get<int>(v);     // ✅ 正确
double val2 = std::get<double>(v); // ❌ 抛 bad_variant_access

// 方式 2:get_if<T>——不确定时用,安全
if (auto* p = std::get_if<double>(&v)) {
    // p 是 double*,当前 v 存的是 double 时才进入
} else if (auto* p = std::get_if<int>(&v)) {
    // 当前 v 存的是 int
}

// 方式 3:visit——最推荐,"我不管是什么类型,你帮我处理每个类型"
std::visit([](const auto& val) {
    // 编译器会为 int/double/string 各生成一个 lambda 实例
    // val 在三个实例中分别是 int、double、string
    std::cout << val;
}, v);

我推荐 visit:因为你不写大量 if-else,不容易漏掉某个类型。而且 visit 是编译期展开的,没有运行时成本。

真实案例:配置文件解析

using ConfigValue = std::variant<int, double, std::string, bool>;

std::unordered_map<std::string, ConfigValue> config;

config["timeout"] = 30;
config["rate"] = 3.14;
config["name"] = "server";
config["enabled"] = true;

// 安全获取配置
template <typename T>
T get_config(const std::string& key, T default_val) {
    auto it = config.find(key);
    if (it == config.end()) return default_val;
    if (auto* p = std::get_if<T>(&it->second)) return *p;
    return default_val;
}

int timeout = get_config<int>("timeout", 10);        // 30
double rate = get_config<double>("rate", 1.0);        // 3.14
bool enabled = get_config<bool>("enabled", false);     // true