红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【C++】std::variant

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: hello

overload 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...>