C++ 中的 variant 解析
C++ 中的 variant 解析
自己写了一个能自动构造的 union,结果发现和标准库的 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
template <typename... Types>
class StaticUnion {
private:
// 定义一个union用于分配空间
template <typename T, typename... Rest>
union StorageUnion {
T first;
StorageUnion<Rest...> rest;
StorageUnion() {}
};
template <typename T>
union StorageUnion<T> {
T first;
StorageUnion() {}
};
// 从 union 中找到对应的类型的成员,其实union中的成员的地址是一样的。
// 用途就是可以不用 reinterpret_cast 将union指针强制转为成员指针
template <class T, class U, class... Rest>
constexpr T *find_(StorageUnion<U, Rest...> &u)
{
if constexpr (std::is_same_v<T, U>) {
return &u.first;
}
else {
return find_<T>(u.rest);
}
}
template <class T>
constexpr T *find_(StorageUnion<T> &u)
{
return &u.first;
}
// 编译期获取类型在模板参数中的索引
template <typename T, u8 Index = 0>
static consteval u8 typeIndex_()
{
static_assert(Index != sizeof...(Types), "Type not in type list");
if constexpr (std::is_same_v<T, std::tuple_element_t<Index, std::tuple<Types...>>>) {
return Index;
}
else {
return typeIndex_<T, Index + 1>();
}
}
// 对指针的封装,可以避免对指针的复制操作,
// 可以管控指针的生命周期,比较像智能指针
template <typename T>
class SlotPtr {
T *ptr;
public:
explicit SlotPtr(T *p) :
ptr(p)
{}
~SlotPtr() = default;
SlotPtr(const SlotPtr &) = delete; // 禁止拷贝
SlotPtr &operator=(const SlotPtr &) = delete;
SlotPtr(SlotPtr &&other) = delete;
SlotPtr &operator=(SlotPtr &&other) = delete;
T *operator->() const { return ptr; }
};
// 用union分配空间,保证空间够用
StorageUnion<Types...> buffer_;
// 运行时的union中的当前激活的成员索引
u8 currentIndex_; // 255 表示空
public:
StaticUnion() :
currentIndex_(255)
{}
// 获取成员
template <typename T>
requires(std::is_same_v<T, Types> || ...)
SlotPtr<T> get()
{
// Types 内不要有重复的(有的话也没什么太大问题)
static_assert(
((0 + ... + (std::is_same_v<T, Types> ? 1 : 0)) == 1),
"T appears zero or multiple times"
);
constexpr u8 targetIndex = typeIndex_<T>();
// 如果当前的激活成员就是想要的,直接返回指针,不用重新构造
if (currentIndex_ == targetIndex) {
return SlotPtr<T>(find_<T>(buffer_));
}
else { // 否则重新构造
// 用到了placement new
T *obj = std::launder(::new (find_<T>(buffer_)) T());
currentIndex_ = targetIndex;
return SlotPtr<T>(obj);
}
}
};
本文由作者按照 CC BY-SA 4.0 进行授权