std::variant
variant实现了union的作用
用法
创建
std::variant<type0,type1,type2,...> 名称
创建一个variant,其中值可以是给定type的任意一种
获取值
get<类型下标/类型名称>(名称)按对应下标的数据类型获取值,若不匹配则抛出异常
名称.index()获取当前值对应数据类型的索引
holds_alternative<类型>(名称)判断variant对象是否含有指定类型的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
#include<variant>
int main(){
std::variant<std::string, int, double> v1;
v1 = "hello";
try{
get<int>(v1);
}
catch(const std::bad_variant_access& e){
std::cout<<"Bad variant access"<<e.what()<<std::endl;
}
if (0==v1.index()){
std::cout <<std::get<0>(v1)<<std::endl;
}
if (std::holds_alternative<std::string>(v1)){
std::cout << std::get<std::string>(v1)<<std::endl;
}
else{
std::cout <<"Not a String"<<std::endl;
}
return 0;
}std::visit
visit函数会根据variant中值的类型来调用自定义对象中对应的函数调用运算符重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
#include<variant>
struct visitor{
void operator()(int i) const{std::cout <<"int: "<<i<<std::endl;}
void operator()(double d) const{std::cout <<"double: "<<d<<std::endl;}
void operator()(const std::string &s) const{std::cout <<"string: "<<s<<std::endl;}
};
int main(){
std::variant<int, double, std::string> v;
v = 6;
std::visit(visitor{}, v);
v = "hello";
std::visit(visitor{}, v);
}1
2
int: 6
string: hellooverload pattern
我们可以使用overload模式使得variant使用更加方便
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<iostream>
#include<variant>
struct A{
void operator()(int i) const{std::cout <<"int: "<<i<<std::endl;}
};
struct B{
void operator()(double d) const{std::cout <<"double: "<<d<<std::endl;}
};
struct C{
void operator()(const std::string &s) const{std::cout <<"string: "<<s<<std::endl;}
};
// 变参类模板
template<class... Ts> // Ts为模板形参包,表示实例化时传入的多个实参
struct Visitor:Ts...{ // 这里的省略号表示将形参包展开,替换为实际的参数列表
using Ts::operator()...; // 基类重载函数都引入成为自己的
};
int main(){
std::variant<int, double, std::string> v;
v = 6;
Visitor<A,B,C> visitor{};
visitor(6);
visitor("hello");
std::visit(visitor, v);
auto a = [](int i){
std::cout <<"int: "<<i <<std::endl;
};
auto b = [](double d){
std::cout <<"double: "<<d <<std::endl;
};
auto c = [](const std::string &s){
std::cout <<"string: "<<s <<std::endl;
};
v = "hello";
// 用lambda函数实例化对象
Visitor<decltype(a), decltype(b), decltype(c)> visitor2{a,b,c};
std::visit(visitor2, v);
v = 6.3;
Visitor visitor3{a,b,c}; // CTAD,让编译器推断类型
std::visit(visitor3, v);
}这里将visitor类模板进行改造
使用class... Ts表示实例化传入多个实参
使用Ts...将形参包展开
使用using Ts::operator()...将基类成员引入作为继承类成员定义,将所有基类中的重载函数引入成为自己的成员函数
以下使用了三种方法:
让Visitor类继承A,B,C三个模板,拥有三个运算符重载
定义三个lambda对象,使用decltype获得他们的类型,用来实例化Vistor模板
使用CTAD,让编译器从对象的初始化器列表来推断模板类型(C++17)
这时编译器会自动将类型推到为第二种方法中的类型,因此我们可以直接将三个lambda函数塞入Visitor函数让编译器自动推导
1
2
3
4
5
6
7
8
std::visit(Visitor{
[](int i){
std::cout <<"int: "<<i <<std::endl;},
[](double d){
std::cout <<"double: "<<d <<std::endl;},
[](const std::string &s){
std::cout <<"string: "<<s <<std::endl;}},
v);这样在C++20以上可以实现,C++17下需要用户自定义推导指引
即将初始化列表实参类型作为模板实参列表
1
2
template<class... Ts>
overload(Ts...) -> overload<Ts...>