3.1 std::variant
代码:https://github.com/leoda1/the-notes-of-cuda-programming/tree/main/code/CPU
0. 前言
std::variant是C++17引入的标准库类型,表示多个可能类型中的一个。类似于联合体(union),但是更安全易于使用。
union分配的内存只管最大的成员,union的所有成员share同一个内存空间。这就导致如下安全问题:
#include <iostream>
union MyUnion {
int intValue;
double doubleValue;
};
int main() {
MyUnion myUnion;
myUnion.doubleValue = 3.14;
std::cout << "Integer Value: " << myUnion.intValue << "\n";
return 0;
}
/*
Integer Value: 1374389535
*/
这里的int变量是未定义类型但是输出了莫名其妙的东西,所以c++17出现的variant就是为了解决这个潜在的问题。
1. variant是什么
std::variant 是对传统 union 的改进,因为它确保了类型安全,并为处理不同类型的值提供了一种方便的方法。
// Variant_exp1.cpp
#include <iostream>
#include <variant>
int main() {
std::variant<int, double> myVariant;
myVariant = 42; /* Store an int */
std::cout << std::get<double>(myVariant) << std::endl;
return 0;
}
// g++ -std=c++17 demo.cpp -o demo
/*
terminate called after throwing an instance of 'std::bad_variant_access'
what(): Unexpected index
Aborted (core dumped)
*/
这里创建的是包含int
和double
的variant
变量,但是只给int
赋值了,在用get
访问variant
的double
变量就报错了。
再来看一个范例
// Variant_exp2.cpp
#include <iostream>
#include <variant>
#include <string>
using namespace std;
int main() {
variant<int, string> myIntStr;
myIntStr = 100;
myIntStr = "leoda";
try {
if (holds_alternative<int>(myIntStr)) {
cout << "value as int is : " << get<int>(myIntStr) << endl;
} else if (holds_alternative<string>(myIntStr)) {
cout << "value as string is : " << get<string>(myIntStr) << endl;
} else {
cout << "error"<< endl;
}
} catch (bad_variant_access& e){
cerr << "Error: " << e.what() << endl;
}
return 0;
}
// g++ -std=c++17 Variant_exp2.cpp -o Variant_exp2
/******************************************************************
value as string is : leoda
*******************************************************************/
2. variant的公共成员函数
默认构造函数:
std::variant<int, double, std::string> myVariant; /* Default construction */
赋值构造函数:
std::variant<int, double> myVariant;
myVariant = 42; /* Assigns an int */
index()
方法返回当前类型的索引:
std::variant<int, float, std::string> var;
var = 42;
std::cout << "Current index: " << var.index() << std::endl; // 输出 0
var = 3.14f;
std::cout << "Current index: " << var.index() << std::endl; // 输出 1
std::variant_alternative
获取类型
用来获取 std::variant
模板参数列表中指定索引处的类型。
std::variant<int, float, std::string> var;
// 获取索引 0 对应的类型
using T0 = std::variant_alternative<0, decltype(var)>::type;
std::cout << "Type at index 0: " << typeid(T0).name() << std::endl; // 输出 int
// 获取索引 1 对应的类型
using T1 = std::variant_alternative<1, decltype(var)>::type;
std::cout << "Type at index 1: " << typeid(T1).name() << std::endl; // 输出 float
valueless_by_exception()
查看variant变量是否赋值了
std::variant<int, double> myVariant;
if (myVariant.valueless_by_exception()) {
/* Handle the case where the variant has no value */
}
综合范例:
/******************************************************************
* Author : Da Liu
* Date : 2025-03-09
* File Name : Variant_exp3.cpp
* Description : variant的部分成员函数的使用
*****************************************************************/
#include <variant>
#include <iostream>
#include <string>
#include <typeinfo>
int main() {
std::variant<int, float, std::string> var = "Hello";
std::cout << "Current index: " << var.index() << std::endl; // 输出 2
if (var.index() == 0) {
using T = std::variant_alternative<0, decltype(var)>::type;
std::cout << "Current type: " << typeid(T).name() << " with value " << std::get<T>(var) << std::endl;
} else if (var.index() == 1) {
using T = std::variant_alternative<1, decltype(var)>::type;
std::cout << "Current type: " << typeid(T).name() << " with value " << std::get<T>(var) << std::endl;
} else if (var.index() == 2) {
using T = std::variant_alternative<2, decltype(var)>::type;
std::cout << "Current type: " << typeid(T).name() << " with value " << std::get<T>(var) << std::endl;
}
return 0;
}
// g++ -std=c++17 Variant_exp3.cpp -o Variant_exp3
/*********************************************************************************************
Current index: 2
Current type: NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE with value Hello
**********************************************************************************************/
Swap:(需要类型一致)
std::variant<int, double> var1 = 42;
std::variant<int, double> var2 = 3.14;
std::swap(var1, var2);
empalce():提供的参数进行就地构造
std::variant<int, std::string> myVariant;
myVariant.emplace<std::string>("Hello");
visit()
用于访问 std::variant
中存储的值。它通过接受一个访问者(visitor)对象来对 std::variant
中当前存储的值进行操作。访问者对象可以是一个函数对象、lambda 表达式或具有重载 operator()
的结构体。基本用法包括:
- 定义一个
std::variant
对象,包含多个可能的类型。 - 创建一个访问者对象,定义对每种可能类型的操作。
- 使用
std::visit
传递访问者对象和std::variant
对象。
范例:
//basic
#include <variant>
#include <iostream>
#include <string>
int main() {
std::variant<int, float, std::string> var = "Hello, World!";
auto visitor = [](auto&& arg) {
std::cout << "Value: " << arg << std::endl;
};
std::visit(visitor, var); // 输出: Value: Hello, World!
var = 42;
std::visit(visitor, var); // 输出: Value: 42
var = 3.14f;
std::visit(visitor, var); // 输出: Value: 3.14
return 0;
}
// 结构体作为访问者
#include <variant>
#include <iostream>
#include <string>
struct Visitor {
void operator()(int i) const {
std::cout << "Integer: " << i << std::endl;
}
void operator()(float f) const {
std::cout << "Float: " << f << std::endl;
}
void operator()(const std::string& s) const {
std::cout << "String: " << s << std::endl;
}
};
int main() {
std::variant<int, float, std::string> var;
var = 42;
std::visit(Visitor{}, var); // 输出: Integer: 42
var = 3.14f;
std::visit(Visitor{}, var); // 输出: Float: 3.14
var = "Hello, World!";
std::visit(Visitor{}, var); // 输出: String: Hello, World!
return 0;
}
// 访问多个变体
#include <variant>
#include <iostream>
#include <string>
struct Visitor {
void operator()(int i, float f) const {
std::cout << "Integer: " << i << ", Float: " << f << std::endl;
}
void operator()(int i, const std::string& s) const {
std::cout << "Integer: " << i << ", String: " << s << std::endl;
}
void operator()(float f, const std::string& s) const {
std::cout << "Float: " << f << ", String: " << s << std::endl;
}
void operator()(const auto& a, const auto& b) const {
std::cout << "Other types: " << a << ", " << b << std::endl;
}
};
int main() {
std::variant<int, float, std::string> var1 = 42;
std::variant<int, float, std::string> var2 = "Hello, World!";
std::visit(Visitor{}, var1, var2); // 输出: Integer: 42, String: Hello, World!
var2 = 3.14f;
std::visit(Visitor{}, var1, var2); // 输出: Integer: 42, Float: 3.14
return 0;
}
3 总结
std::variant<T, U, ...>
代表一个多类型的容器,容器中的值是制定类型的一种,是通用的 Sum Type,对应 Rust 的enum
。是一种类型安全的union
,所以也叫做tagged union
。与union
相比有两点优势:
- 可以存储复杂类型,而 union 只能直接存储基础的 POD 类型,对于如
std::vector
和std::string
就等复杂类型则需要用户手动管理内存。 - 类型安全,variant 存储了内部的类型信息,所以可以进行安全的类型转换,c++17 之前往往通过
union
+enum
来实现相同功能。
通过使用std::variant<T, Err>
,用户可以实现类似 Rust 的std::result
,即在函数执行成功时返回结果,在失败时返回错误信息,上文的例子则可以改成:
std::variant<ReturnType, Err> func(const string& in) {
ReturnType ret;
if (in.size() == 0)
return Err{"input is empty"};
// ...
return {ret};
}
1