此头文件是通用工具库的一部分。类模板 std::variant
表示一个类型安全的联合体(以下称“变体”)。一个 std::variant
的实例在任意时刻要么保有它的可选类型之一的值,要么在错误情况下无值(此状态难以达成,见 valueless_by_exception)。
与联合体类似,如果变体保有某个对象类型 T
的值,那么 T
的对象表示会在变体自身的对象表示中直接分配。不允许变体分配额外的(动态)内存。
变体不能保有引用、数组,或类型 void。
变体可以保有同一类型多于一次,而且可保有同一类型的不同 cv 限定版本。
与联合体在聚合初始化中的行为一致,默认构造的变体保有它的首个选项的值,除非该选项不可默认构造(此时该变体也不可默认构造)。可以用辅助类 std::monostate 使这种变化体可默认构造。
如果程序在没有提供任何模板实参的情况下实例化了 std::variant
的定义,那么程序非良构。此时可以使用 std::variant<std::monostate> 代替。
如果程序声明了 std::variant
的显式或部分特化,那么程序非良构,不要求诊断。
成员函数
构造 variant 对象(公开成员函数) | |
析构 variant 和它包含的值(公开成员函数) | |
赋值 variant (公开成员函数) | |
观察器 | |
返回 variant 所保有可选项的零基索引(公开成员函数) | |
检查 variant 是否在非法状态(公开成员函数) | |
修改器 | |
原位构造 variant 中的值(公开成员函数) | |
与另一 variant 交换(公开成员函数) | |
观览 | |
(C++26) | 以 variant 所保有的实参调用提供的函数对象(公开成员函数) |
非成员函数
(C++17) | 以一或多个 variant 所保有的各实参调用所提供的函数对象(函数模板) |
(C++17) | 检查某个 variant 是否当前持有某个给定类型(函数模板) |
(C++17) | 以给定索引或类型(如果类型唯一)读取 variant 的值,错误时抛出异常(函数模板) |
(C++17) | 以给定索引或类型(如果唯一),获得指向被指向的 variant 的值的指针,错误时返回空指针(函数模板) |
operator==operator!=operator<operator<=operator>operator>=operator<=> (C++17)(C++17)(C++17)(C++17)(C++17)(C++17)(C++20) | 以所含值比较 variant 对象(函数模板) |
(C++17) | 特化 std::swap 算法 (函数模板) |
辅助类
(C++17) | 用作非可默认构造类型的 variant 的首个可选项的占位符类型(类) |
(C++17) | 非法地访问 variant 的值时抛出的异常(类) |
(C++17) | 在编译时获得 variant 可选项列表的大小(类模板) (变量模板) |
variant_alternativevariant_alternative_t (C++17) | 在编译时获得按索引指定的可选项的类型 (类模板) (别名模板) |
(C++17) | std::variant 的散列支持 (类模板特化) |
辅助对象
(C++17) | 非法状态的 variant 的下标(常量) |
示例代码:
#include <cassert>
#include <iostream>
#include <string>
#include <variant>
#include <type_traits>
#include <iomanip>
std::ostream& operator<<(std::ostream& os, std::variant<int, std::string> const& va)
{
os << ": { ";
std::visit([&](auto&& arg)
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
os << arg;
else if constexpr (std::is_same_v<T, std::string>)
os << std::quoted(arg);
}, va);
return os << " };\n";
}
struct Demo {
Demo(int) {}
Demo(const Demo&) { throw std::domain_error("复制构造函数"); }
Demo& operator= (const Demo&) = default;
};
int main()
{
std::variant<int, float> v, w;
v = 42; // v 含 int
int i = std::get<int>(v);
assert(42 == i); // 成功
w = std::get<int>(v);
w = std::get<0>(v); // 与前一行效果相同
w = v; // 与前一行效果相同
std::cout << "i==========================================" << i << "\n";
// std::get<double>(v); // 错误:[int, float] 中无 double
// std::get<3>(v); // 错误:有效索引值为 0 与 1
try
{
std::get<float>(w); // w 含 int 而非 float:会抛出异常
}
catch (const std::bad_variant_access& ex)
{
std::cout << ex.what() << '\n';
}
using namespace std::literals;
std::variant<std::string> x("abc");
// 转换构造函数在无歧义时起作用
x = "def"; // 转换赋值在无歧义时亦起作用
std::variant<std::string, void const*> y("abc");
// 传递 char const * 时转换成 void const *
assert(std::holds_alternative<void const*>(y)); // 成功
y = "xyz"s;
assert(std::holds_alternative<std::string>(y)); // 成功
//index / get example
std::variant<int, std::string> v1 = "abc", v2;
std::cout << "v1.index========" << v1.index() << "\n";
std::cout << "v2.index========" << v2.index() << "\n";
std::cout << "v1==============" << std::get<std::string>(v1) << "\n";
v1 = {};
v2 = "ssss";
std::cout << "v1.index========" << v1.index() << "\n";
std::cout << "v2.index========" << v2.index() << "\n";
v1 = 20;
std::cout << "v1==============" << std::get<int>(v1) << "\n";
std::string strV2 = std::get<std::string>(v2);
std::cout << "strV2===========" << strV2 << "\n";
//operator= example
std::variant<int, std::string> a{ 2017 }, b{ "CppCon" };
std::cout << "a=" << a << "b=" << b << '\n';
std::cout << "(1) operator=( const variant& rhs )\n";
a = b;
std::cout << "a=" << a << "b=" << b << '\n';
std::cout << "(2) operator=( variant&& rhs )\n";
a = std::move(b);
std::cout << "a=" << a << "b=" << b << '\n';
std::cout << "(3) operator=( T&& t ), 其中 T 为 int\n";
a = 2019;
std::cout << "a=" << a << '\n';
std::cout << "(3) operator=( T&& t ), 其中 T 为 std::string\n";
std::string s{ "CppNow" };
std::cout << "s=: " << std::quoted(s) << '\n';
a = std::move(s);
std::cout << "a=" << a << "s=: " << std::quoted(s) << '\n';
//emplace example
std::variant<std::string> v3;
v3.emplace<0>("abc"); // OK
std::cout << "std::get<0>(v3)==============" << std::get<0>(v3) << '\n';
v3.emplace<std::string>("def"); // OK
std::cout << "std::get<0>(v3)==============" << std::get<0>(v3) << '\n';
std::variant<std::string, std::string> v4;
v4.emplace<1>("ghi"); // OK
std::cout << "std::get<1>(v4)==============" << std::get<1>(v4) << '\n';
// v4.emplace<std::string>("abc"); -> 错误
std::variant<std::string> v5 = "sccot";
std::cout << "std::get<0>(v5)==============" << std::get<0>(v5) << '\n';
v5.swap(v3);
std::cout << "std::get<0>(v5)==============" << std::get<0>(v5) << '\n';
std::cout << "std::get<0>(v3)==============" << std::get<0>(v3) << '\n';
//valueless_by_exception example
std::variant<std::string, Demo> var{ "str" };
assert(var.index() == 0);
assert(std::get<0>(var) == "str");
assert(var.valueless_by_exception() == false);
try {
var = Demo{ 666 };
}
catch (const std::domain_error& ex) {
std::cout << "1) 异常: " << ex.what() << '\n';
}
assert(var.index() == std::variant_npos);
assert(var.valueless_by_exception() == true);
// 现在 var “无值”,此为 var 的初始化过程中引发的异常导致的非法状态。
try {
std::get<1>(var);
}
catch (const std::bad_variant_access& ex) {
std::cout << "2) 异常: " << ex.what() << '\n';
}
var = "str2";
assert(var.index() == 0);
assert(std::get<0>(var) == "str2");
assert(var.valueless_by_exception() == false);
//holds_alternative example
std::variant<int, std::string> v6 = "abc";
std::cout << std::boolalpha
<< "变体持有 int? "
<< std::holds_alternative<int>(v6) << '\n'
<< "变体持有 string? "
<< std::holds_alternative<std::string>(v6) << '\n';
//get_if example
auto check_value = [](const std::variant<int, float>& v)
{
if (const int* pval = std::get_if<int>(&v))
std::cout << "变体值: " << *pval << '\n';
else
std::cout << "获取值失败!" << '\n';
};
std::variant<int, float> v7{ 12 }, w7{ 3.f };
check_value(v7);
check_value(w7);
std::cout << "hello world\n";
}
运行结果:
参考: