std::variant
简介
std::variant 是c++17 引入的一个类型,其作用类似于C语言中的Union,但是比Union 的功能强大的多。 C语言中一个联合体Union 可以储存多种类型数据,但缺点有很多。比如: 1 没有可用的方法来判断Union中真实储存的类型,获取值时也是内存拷贝的结果,可能会存在问题。这就只能靠程序员人脑保证获取的值是有意义的。 2 只能储存基础数据类型,不能储存其他结构体
使用 std::variant
声明一个variant对象很容易,我们可以利用std::variant 和 std::vector 将不同类型的数据放到一起,形成类似弱类型语言的效果 (例如javascript),这一点将非常有用。
#include <variant>
using value = std::variant<int, double, bool, std::string>;
std::vector<value> values = {0, true, 3.1415926,"hello"};
如何取出一个std::variant对象的值呢? 有一系列的方法判断一个 std::variant 对象的类型, 例如
-
index() 方法, 返回 variant 对象类型index, 当一个std::variant 对象没有被初始化的时候,会发生什么呢?会默认按照第一个类型进行初始化,那如果第一个类型没有默认的构造函数会怎么样呢?编译器会报错,为了解决这个问题,std::引入了 std::monostate 作为占位的类型。也可以使用它来替代一部分 std::optional 的功能。 -
holds_alternative<> 判断 variant 是否是某种特定类型 -
std::get<> 方法获取类型的值,类型不正确会抛exception,模板参数可以是类型,也可以是 index值, 但必须是常量值,因为模板是编译期推断 -
std::get_if<> 推荐使用的方法,大部分c++项目都是禁止try- catch 的,因此无异常版本更加实用。 该方法在类型不正确会返回空,输入和输出都是指针类型
#include <variant>
using value = std::variant<int, double, bool, std::string>;
value = "hello, world";
std::cout<<value.index()<<std::end;
std::cout<<std::holds_alternative<std::string><<std::end;
std::get<3>(value);
std::get<std::string>(value);
auto* v = std::get_if<std::string>(&value);
if (v){
std::cout<<*v<<std::end;
}
其他:
当模板参数列表与传入值匹配度相近时,会产生错误,这时就需要使用in_place_type 指定赋值的类型 比如std::variant<float,double> v = 1.0; 这时候就需要使用
std::visit
简介
std::visit 是配合std::variant的一个访问器,可以以函数式方法访问std::variant变量。
你可能疑惑:上面不是说过使用get_if<>来访问std::variant 吗? 怎么这会儿又多出来一个std::visit ? 我们先看例子 假设我们想访问一个variant 变量,根据它的类型做不同的操, 如果不使用std::visit 会怎么样?
void Print(std::variant<int, float, string>& v) {
int index = v.index();
if (index == 0) {
int value = std::get<0>(v);
cout << "int: " << value << '\n';
} else if (index == 1){
float value = std::get<1>(v);
cout << "float: " << f << '\n';
} else {
std::string str = std::get<2>(v);
cout << "str: " << str << '\n';
}
}
int main() {
std::variant<int, float, string> value = 1.0;
Print(value);
}
以上代码使用 holds_alternative 或者 std::get_if<>也是类似的。
接下来使用 std::visit 实现这个功能
struct PrintVisitor {
void operator()(int i) { cout << "int: " << i << '\n'; }
void operator()(float f) { cout << "float: " << f << '\n'; }
void operator()(const std::string& s) { cout << "str: " << s << '\n'; }
};
int main() {
std::variant<int, float, string> value = 1.0;
std::visit(PrintVisitor{}, value);
}
也可以利用C++17 新增的 overloaded 模板,可以直接生成匿名访问器,简化代码, 下面的代码是等价的
int main() {
std::variant<int, float, string> value = 1.0;
std::visit(overloaded{
void operator()(int i) { cout << "int: " << i << '\n'; }
void operator()(float f) { cout << "float: " << f << '\n'; }
void operator()(const std::string& s) { cout << "str: " << s << '\n'; }
},value);
}
好像也看不出多少区别?
接下来还有第三种方法来访问std::variant
int main()
{
std::variant<int, float, string> value = 1.0;
std::visit(
[&](auto &&arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr(std::is_same_v<T,int>) {
cout << "int: " << arg << '\n';
} else if constexpr(std::is_same_v<T,float>){
cout<< "float: "<< arg <<'\n';
} else if constexpr(std::is_same_v<T,std::string>){
cout<< "str: "<< arg <<'\n';
}
}, value);
}
这里我们可以看出来第三种写法比第一种的优势在哪里了:编译期推断。 第三种方法由于使用了constexpr 进行 if 分支的判断,因此是在编译期运行,而第一种方法是运行期进行类型判断,效率是不同的。 第二种方法和模板一样,也是编译期推断的,因此效率也是很高的,所以我们应当尽量使用 std::visit 方法来访问variant 变量
另外std::visit 还有一个好处是,它的参数列表是不定长的,我们可以传入多个variant 变量
template <class Visitor, class... Variants>
constexpr visit(Visitor&& vis, Variant&&... vars);
|