std::variant 本身不支持空状态,需用 std::monostate 作为占位类型显式表示“什么都没有”;它零大小、可默认构造,放入 variant 类型列表后可通过赋值或检查实现空语义。

std::variant 怎么表示“什么都没有”
它本身不支持空状态——std::variant 构造时必须持有其某个可选项类型的合法值,没有类似 std::nullopt 的内置空标记。想表达“尚未初始化”或“无效值”,得自己铺一层逻辑。
为什么不用 std::optional<:variant>>?
能用,但多数时候是绕远路:你本意是让 variant 承载一组类型,结果又套一层 optional,语义变重了(变成“可能有 variant,也可能完全没有”),还多一次内存布局开销(optional 通常加 1 字节 tag)。更关键的是,它掩盖了真正的问题——你其实想要的是 variant 内部的“空”语义,而不是外层可选性。
-
std::optional<:variant std::string>></:variant>表示:可能有一个 int、可能有一个 string、也可能啥都没有 - 而你需要的是:
std::variant<:monostate int std::string></:monostate>表示:要么是占位符(空)、要么是 int、要么是 string -
std::monostate是标准库提供的零大小、无状态、可比较、可默认构造的类型,专为这种场景设计
std::monostate 在 variant 中的实际用法
把它当作一个“哑元类型”塞进 variant 的类型列表最前面或最后面,然后用 std::holds_alternative<:monostate></:monostate> 判断是否为空;用 std::get<:monostate></:monostate> 或直接赋值 std::monostate{} 来清空当前值。
std::variant<std::monostate, int, std::string> v;
v = 42; // 现在 hold int
v = std::string{"hello"}; // 现在 hold string
v = std::monostate{}; // 现在 hold monostate —— 即“空”
if (std::holds_alternative<std::monostate>(v)) {
// 这里就是空分支
}
- 顺序不重要,但建议把
std::monostate放第一位,这样默认构造后自动进入空状态(std::variant默认构造会初始化第一个类型) - 不能对
std::monostate做任何实质操作(它没成员、没状态),它的唯一作用是占位和区分 - 注意:如果 variant 已经 hold 其他类型,再赋
std::monostate{}会先析构旧值——确保旧类型的析构是安全的
容易踩的坑:空状态 ≠ 默认初始化失败
有人误以为加了 std::monostate 就能捕获构造异常并自动回退到空,其实不是。std::variant 构造失败(比如某类型构造抛异常)会导致整个构造失败,不会静默转成 std::monostate。空状态必须显式设置。
立即学习“C++免费学习笔记(深入)”;
- 错误写法:
std::variant<:monostate risky_type> v{risky_value};</:monostate>—— 如果risky_type构造失败,程序直接异常终止,不会变成std::monostate - 正确做法:用 try-catch 包裹,捕获后手动赋
std::monostate{} - 另一个坑:用
std::get(v)强制取第一个 alternative,但如果当前 hold 的不是std::monostate(比如是 int),就会抛std::bad_variant_access—— 务必先用std::holds_alternative检查
monostate 不是银弹,它只解决“表示空”的接口问题,不解决初始化安全性或错误恢复逻辑。这部分还得你自己控制流程。









