得之我幸 失之我命

when someone abandons you,it is him that gets loss because he lost someone who truly loves him but you just lost one who doesn’t love you.

Cpp17 variant

std::variant

std::variant 是 C++17 中一个新加入标准函式库的 template 容器,使用时需要 #include

它的概念基本上是和 union 一样,是一个可以用来储存多种型别资料的容器;和 union 不同的是,std::variant 是 type-safe 的,再加上有许多函数可以搭配使用,所以在使用上应该算是相对安全

由于它是标准函式库的 template class,在使用时不需要另外去宣告一个新的类型

std::variant 在储存数据的时候,内部会有一个索引值来记录目前存储的是哪一个类型的数据

std::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
std::variant<int, double, std::string> x, y;

// 赋值操作
x = 1.0;
y = "1.0";
x = 2; // overwrite value

// 获取当前使用的 type 在 variant 声明中的索引
std::cout << "x - " << x.index() << std::endl; // x - 0
std::cout << "y - " << y.index() << std::endl; // y - 2

// 使用 std::get<type>() 或直接 std::get<index>() 来获取 std::variant 中包含的值
std::cout << std::get<int>(x) << std::endl; // 2
std::cout << std::get<double>(x) << std::endl; // 如果 std::variant 中当前存储的不是对应 type 的值,则会抛出 std::bad_variant_access 类型的异常
std::cout << std::get<2>(y) << std::endl; // 1.0
std::cout << std::get<1>(y) << std::endl; // 如果 index 和变量不对应,也会抛出 std::bad_variant_access 类型的异常

// 除了会引发异常的 std::get<>,也有无异常的 std::get_if<> 方法,需要自行判断返回的指针类型是否为空
int* i = std::get_if<int>(&x);
if (i == nullptr)
{
std::cout << "wrong type" << std::endl;
}
else
{
std::cout << "value is " << *i << std::endl;
}

std::visit

std::variant 的好搭档 std::visit,是 C++ 标准库中的一个函数,它的作用是将一个包含多种不同类型的可访问对象的容器中的对象传递给多个可调用对象中的一个,具体调用哪个可调用对象由该对象所包含的对象的实际类型决定;与直接使用 std::get 来获取值并手动判断其类型不同,std::visit 可以在编译时自动匹配相应的处理函数,清晰易读

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
std::variant<int, std::string> v;

v = "test";
// 配合 lambda 使用
std::visit([](auto&& arg) { std::cout << arg << '\n'; }, v); // test

v = 1;
struct SOutput
{
// 由于这部分会在编译阶段做检查,所以 SOutput 要确定有针对所有可能的类型,都撰写对应的函数,如果有缺的话,在编译阶段就不会过了
void operator()(const int& i)
{
std::cout << i << std::endl;
}
void operator()(const std::string& s)
{
std::cout << s << std::endl;
}
};
struct SOutput // 使用 template 重写 SOutput,减少重复的代码
{
template<typename TYPE>
void operator()(const TYPE& v)
{
std::cout << v << std::endl;
}
};

std::visit(SOutput(), v);

std::optional

std::optional 是一种 sum type,除了类型 T,它还有一个特殊的类型 std::nullopt_t 表示 “什么都没有” 的状态,这个类型与 std::nullptr_t 一样,只有一个值 std::nullopt,std::optional 在没有设置值的情况下类型就是 std::nulopt_t,值为 std::nullopt

std::optional 也提供了一系列和智能指针相似的接口:可以显式地转化为 bool 型变量来显示它此时是否拥有一个有意义的值;指针的解引用操作符 * 和 -> 也被重载,使用时需要注意它不会去检测是否 has_value(),也不会抛出异常,更加高效,但是要注意安全,当访问没有 value 的 std::optional 的时候,行为是未定义的

reset() 方法销毁存储在 std::optional 中的值,并将其值为空

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
std::optional<unsigned> opt = 2;
// 通过 has_value() 来判断对应的 std::optional 是否处于已经设置值的状态
if (opt.has_value())
// 使用 * 操作符获取值
std::cout << *opt << std::endl; // 2
// 通过 value() 来获取 std::optional 对象中存储的值
std::cout << opt.value() << std::endl; // 2

class A
{
public:
int a = 0;
};
int main()
{
A main_a;
std::optional<A> opt = main_a;
std::cout << opt.value().a << std::endl; // 0
if (opt)
{
// 如果 std::optional 里的类型是结构体,也可以用使用 -> 操作符来访问该结构体的属性
std::cout << opt->a << std::endl; // 0
std::cout << (*opt).a << std::endl; // 0
}
}

std::optional<unsigned> opt;
if (!opt.has_value())
// 通过 value_or() 来获取 std::optional 对象中存储的值,value_or() 可以允许传入一个默认值,如果 std::optional 为 std::nullopt,则直接返回传入的默认值
std::cout << opt.value_or(0) << ".\n"; // 0.
std::cout << opt.value() << ".\n"; // 当没有 value 时调用该方法将 throws std::bad_optional_access 异常

be slow to promise and quick to perform.