参考来源:知乎_铁甲万能狗_C/C++ 修道院
参考来源:cppreference
参考来源:现代C++教程:第 4.3 节
varaint
可以看做增强的Union
变体,优点:
重要函数:
index
:返回当前可变体内部对应的数据类型的索引 std::variant<int, std::string> v = "abc";
std::cout << "v.index = " << v.index() << '\n'; // v.index = 1
v = {};
std::cout << "v.index = " << v.index() << '\n'; // v.index = 0
std::get
:获取可变体对应的数据类型的值std::variant<int, float> v{12}, w;
std::cout << std::get<int>(v) << '\n'; // 12
w = std::get<int>(v);
w = std::get<0>(v);
// std::get(v); // error: no double in [int, float]
// std::get<3>(v); // error: valid index values are 0 and 1
try {
w = 42.0f;
std::cout << std::get<float>(w) << '\n'; // ok, prints 42
w = 42;
std::cout << std::get<float>(w) << '\n'; // throws
} catch (std::bad_variant_access const& ex) {
std::cout << ex.what() << ": w contained int, not float\n";
}
std::get_if
:获取可变体对应的数据类型的值,在访问可变体时不会抛出bad_variant_access
异常,提供了访问前的类型安全判断 auto check_value = [](const std::variant<int, float>& v) {
if(const int* pval = std::get_if<int>(&v)) {
std::cout << "variant value: " << *pval << '\n';
} else {
std::cout << "failed to get value!" << '\n';
}
};
std::variant<int, float> v{12}, w{3.f};
check_value(v); // variant value: 12
check_value(w); // failed to get value!
std::holds_alternative
:检查可变体对应类型是否是可切换的类型T
std::variant<int, std::string> v = "abc";
std::cout << std::boolalpha << std::holds_alternative<int>(v) << '\n'; // flase
std::cout << std::holds_alternative<std::string>(v) << '\n'; // true
std::variant_size_v
:用于检测可变体内部可切换的数据类型的个数std::variant<int, float, std::string> v;
static_assert(std::variant_size_v<decltype(v)> == 3);
std::visit
:用于访问可变体中的当前处于活动状态的数据类型的实例// the variant to visit
using var_t = std::variant<int, long, double, std::string>;
// helper constant for the visitor #3
template<class>
inline constexpr bool always_false_v = false;
int main()
{
std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
for (auto& v: vec) {
// 1. void visitor, only called for side-effects (here, for I/O)
std::visit([](auto&& arg){ std::cout << arg; }, v);
// 2. value-returning visitor, demonstrates the idiom of returning another variant
var_t w = std::visit([](auto&& arg) -> var_t { return arg + arg; }, v);
// 3. type-matching visitor: a lambda that handles each type differently
std::cout << ". After doubling, variant holds ";
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int with value " << arg << '\n';
else if constexpr (std::is_same_v<T, long>)
std::cout << "long with value " << arg << '\n';
else if constexpr (std::is_same_v<T, double>)
std::cout << "double with value " << arg << '\n';
else if constexpr (std::is_same_v<T, std::string>)
std::cout << "std::string with value " << std::quoted(arg) << '\n';
else
static_assert(always_false_v<T>, "non-exhaustive visitor!");
}, w);
}
/* 输出:
10. After doubling, variant holds int with value 20
15. After doubling, variant holds long with value 30
1.5. After doubling, variant holds double with value 3
hello. After doubling, variant holds std::string with value "hellohello"
*/
}
std::variant<std::string, double, float, int> v;
std::cout << v.index() << "\n"; // 0
class Item {
public:
Item(int, float) {}
};
class Items {
public:
Items() = default;
Items(int, float) {}
};
int main()
{
// std::variant- v1;
// No matching constructor for initialization of
// 'std::variant- '
// (aka 'variant- , allocator
>, int>')
std::variant<std::string, double, Item, int> v2;
std::cout << v2.index() << "\n"; // 0
v2 = Item(1, 2.);
std::cout << v2.index() << "\n"; // 0
std::variant<Items, double, std::string, int> v3;
std::cout << v3.index() << "\n"; // 0
return 0;
}
std::variant<std::string, double, float, int> v = 12.; // 目标是 float, 但编译器将匹配数据类型的最大精度: double
std::cout << v.index() << "\n"; // 1
std::variant<std::string, double, float, int> v = 12.f;
std::cout << v.index() << "\n"; // 2
std::in_place_index
告知可变体对象要在内置启用哪一个数据类型来构造可变体对象的实例std::tuple<std::string, double, int> t("123", 4.5, 8);
std::variant<std::string, double, int> v {std::in_place_index<1>, std::get<1>(t)};
std::variant<std::string, double, int> vs {std::in_place_index<0>, "123"};
get
方法获取真正的对象,然后修改API
匹配数据类型,然后构造传值达到修改值的目的emplace
方法赋值class Item {
public:
Item(int, float) {}
};
int main() {
using MixType = std::variant<int, float, std::vector<int>, std::string, Item>;
MixType v;
// 方式1: 赋值操作符
v= 12;
std::cout << std::get<0>(v) << "\n"; // 12
// 方式2: 通过 get 方法获取真正的对象, 然后修改
v = 23.5f;
std::cout << std::get<1>(v) << "\n"; // 23.5
// 方式3: 通过原地索引 API 匹配数据类型, 然后构造传值达到修改值的目的
v = MixType(std::in_place_index<2>, { 1, 2, 3 });
std::cout << std::get<2>(v).size() << "\n"; // 3
// 方式4: emplace 方法赋值
v.emplace<3>("hello world!");
std::cout << std::get<3>(v) << "\n"; // hello world!
return 0;
}
class Items {
public:
Items() = default;
Items(int, float) {}
~Items() { std::cout << "Deconstruct!\n"; }
};
int main()
{
using MixType = std::variant<int, float, std::vector<int>, std::string, Items>;
MixType v;
v.emplace<4>(1, 2.); // Deconstruct!
v = 1;
std::cout << std::get<0>(v) << "\n"; // 1
}
template <size_t n, typename... T>
constexpr std::variant<T...> _tuple_index(const std::tuple<T...>& tpl, size_t i)
{
if constexpr (n >= sizeof...(T)) {
// throw std::out_of_range(" 越界.");
exit(-1);
}
if (i == n) {
return std::variant<T...>{ std::in_place_index<n>, std::get<n>(tpl) };
}
return _tuple_index<(n < sizeof...(T) - 1 ? n + 1 : 0)>(tpl, i);
}
template <typename... T>
constexpr std::variant<T...> tuple_index(const std::tuple<T...>& tpl, size_t i)
{
return _tuple_index<0>(tpl, i);
}
template <typename... Ts>
std::ostream & operator<< (std::ostream & s, std::variant<Ts...> const & v)
{
std::visit([&](auto && x){ s << x;}, v);
return s;
}
int main() {
std::tuple<std::string, double, int> t("123", 4.5, 8);
std::cout << tuple_index(t, 0) << "\n"; // 123
std::cout << tuple_index(t, 1) << "\n"; // 4.5
std::cout << tuple_index(t, 2) << "\n"; // 8
return 0;
}
通过实现一个甚至多个可变体对象以引用的方式传递给std::visit
回调的函数进行访问
int main()
{
auto say = [] (const auto& t) {
std::cout << t << ", nice to meet you!" << "\n";
};
std::variant<int, float, std::string> man = {"Bob"};
std::visit(say, man); // Bob, nice to meet you!
}
class CalculatorVisitor {
public:
float mFactor;
explicit CalculatorVisitor (float factor) : mFactor(factor)
{}
void operator()(int& i) const
{
i *= static_cast<int>(mFactor);
}
void operator()(float & i) const
{
i *= static_cast<float>(mFactor);
}
void operator()(std::vector<int> & arr) const
{
for (int k : arr) {
k *= 2;
}
}
};
int main()
{
std::variant<int, float, std::vector<int>> v = 2.5f;
std::visit(CalculatorVisitor(0.97f), v);
std::cout << std::get<1>(v) << "\n"; // 2.425
return 0;
}
调用过程:
CalculatorVisitor
进行实例化std::visit
函数
CalculatorVisitor
实例std::visit
调用了访问者CalculatorVisitor
的构造函数,意味着std::visit
方法持有了该访问者对象public
公开的所有成员函数的上下文的控制权,因此std::visit
可以要求CalculatorVisitor
实现不同的行为std::visit
通过匹配不同重载版本的operator()
运算符的函数指针// the variant to visit
using var_t = std::variant<int, long, double, std::string>;
// helper type for the visitor
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
int main()
{
std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
for (auto& v: vec) {
// 4. another type-matching visitor: a class with 3 overloaded operator()'s
// Note: The `(auto arg)` template operator() will bind to `int` and `long`
// in this case, but in its absence the `(double arg)` operator()
// *will also* bind to `int` and `long` because both are implicitly
// convertible to double. When using this form, care has to be taken
// that implicit conversions are handled correctly.
std::visit(overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
}, v);
}
// 输出: 10 15 1.500000 "hello"
return 0;
}
int main()
{
std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
for (auto& vs: vec) {
std::visit(overloaded {
[](auto && value) {
if constexpr (std::is_same_v<decltype(value), int&>) {
value = 2 * value + 1;
std::cout << value << ' ';
} else if constexpr (std::is_same_v<decltype(value), double&>) {
value = 3 * 1.5f * (value - 1);
std::cout << value << ' ';
} else if constexpr (std::is_same_v<decltype(value), std::string&>) {
std::cout << "\"" << value << "\"\n";
}
}
}, vs);
}
// 输出: 21 2.25 "hello"
return 0;
}
class Node;
using Int = int;
using Float = float;
using Double = double;
using String = std::string;
using Null = std::monostate;
using NodeList = std::vector<Node>;
using Data = std::variant<Int, Float, Double, String, Null, NodeList>;
class Node {
public:
std::string mName;
Data mData;
Node(std::string&& name, Data&& data) : mName(name), mData(data) {}
};
template<typename Stream>
Stream& operator<<(Stream& stream, const Null& null)
{
stream << "null";
return stream;
}
template<typename Stream>
Stream& operator<<(Stream& stream, const NodeList& data)
{
std::cout << "[ ";
for (const auto& d : data) {
std::cout << d << ",";
}
std::cout << ']';
return stream;
}
template<typename Stream>
Stream& operator<<(Stream& stream, Data& data)
{
std::visit([&](auto& val) {
stream << val;
}, data);
return stream;
}
template<typename Stream>
Stream& operator<<(Stream& stream, const Node& node)
{
std::cout << "Node { name='" << node.mName << "', data=" << node.mData << " }";
return stream;
}
struct Person{
std::string name;
int age;
};
template<typename T, typename Fn>
NodeList ToTree(const std::vector<T>& list, Fn callback)
{
NodeList result;
for (const auto& v: list) {
result.push_back(callback(v));
}
return result;
}
int main()
{
auto root = Node(
"Match", NodeList({
Node("TeamA", NodeList({
Node("AA", 72),
Node("AB", 55),
Node("AC", 69)
})),
Node("TeamB", NodeList({
Node("BA", 80),
Node("BB", 57),
Node("BC", 64)
}))
})
);
std::cout << root << "\n";
std::vector<Person> vp = { { "Bob", 10 }, { "Jane", 22 } };
auto pRoot = ToTree(vp, [] (auto&& arg) {
std::string name = arg.name;
Node x(std::move(name), arg.age);
return x;
});
std::cout << pRoot << "\n";
return 0;
}