1 | // Originally taken from |
2 | // https://github.com/cryfs/cryfs/blob/14ad22570ddacef22d5ff139cdff68a54fc8234d/src/cpp-utils/either.h |
3 | |
4 | #pragma once |
5 | |
6 | #include <c10/macros/Macros.h> |
7 | #include <c10/util/C++17.h> |
8 | #include <c10/util/Optional.h> |
9 | |
10 | namespace c10 { |
11 | /** |
12 | * either<A, B> is a tagged union that holds either an object of type A |
13 | * or an object of type B. |
14 | */ |
15 | template <class Left, class Right> |
16 | class either final { |
17 | public: |
18 | template < |
19 | class Head, |
20 | class... Tail, |
21 | std::enable_if_t< |
22 | std::is_constructible<Left, Head, Tail...>::value && |
23 | !std::is_constructible<Right, Head, Tail...>::value>* = nullptr> |
24 | either(Head&& construct_left_head_arg, Tail&&... construct_left_tail_args) |
25 | : _side(Side::left) { |
26 | _construct_left( |
27 | std::forward<Head>(construct_left_head_arg), |
28 | std::forward<Tail>(construct_left_tail_args)...); |
29 | } |
30 | |
31 | template < |
32 | class Head, |
33 | class... Tail, |
34 | std::enable_if_t< |
35 | !std::is_constructible<Left, Head, Tail...>::value && |
36 | std::is_constructible<Right, Head, Tail...>::value>* = nullptr> |
37 | either(Head&& construct_right_head_arg, Tail&&... construct_right_tail_args) |
38 | : _side(Side::right) { |
39 | _construct_right( |
40 | std::forward<Head>(construct_right_head_arg), |
41 | std::forward<Tail>(construct_right_tail_args)...); |
42 | } |
43 | |
44 | either(const either<Left, Right>& rhs) : _side(rhs._side) { |
45 | if (_side == Side::left) { |
46 | _construct_left( |
47 | rhs._left); // NOLINT(cppcoreguidelines-pro-type-union-access) |
48 | } else { |
49 | _construct_right( |
50 | rhs._right); // NOLINT(cppcoreguidelines-pro-type-union-access) |
51 | } |
52 | } |
53 | |
54 | either(either<Left, Right>&& rhs) noexcept : _side(rhs._side) { |
55 | if (_side == Side::left) { |
56 | _construct_left(std::move( |
57 | rhs._left)); // NOLINT(cppcoreguidelines-pro-type-union-access) |
58 | } else { |
59 | _construct_right(std::move( |
60 | rhs._right)); // NOLINT(cppcoreguidelines-pro-type-union-access) |
61 | } |
62 | } |
63 | |
64 | ~either() { |
65 | _destruct(); |
66 | } |
67 | |
68 | either<Left, Right>& operator=(const either<Left, Right>& rhs) { |
69 | _destruct(); |
70 | _side = rhs._side; |
71 | if (_side == Side::left) { |
72 | _construct_left( |
73 | rhs._left); // NOLINT(cppcoreguidelines-pro-type-union-access) |
74 | } else { |
75 | _construct_right( |
76 | rhs._right); // NOLINT(cppcoreguidelines-pro-type-union-access) |
77 | } |
78 | return *this; |
79 | } |
80 | |
81 | either<Left, Right>& operator=(either<Left, Right>&& rhs) { |
82 | _destruct(); |
83 | _side = rhs._side; |
84 | if (_side == Side::left) { |
85 | _construct_left(std::move( |
86 | rhs._left)); // NOLINT(cppcoreguidelines-pro-type-union-access) |
87 | } else { |
88 | _construct_right(std::move( |
89 | rhs._right)); // NOLINT(cppcoreguidelines-pro-type-union-access) |
90 | } |
91 | return *this; |
92 | } |
93 | |
94 | bool is_left() const noexcept { |
95 | return _side == Side::left; |
96 | } |
97 | |
98 | bool is_right() const noexcept { |
99 | return _side == Side::right; |
100 | } |
101 | |
102 | const Left& left() const& { |
103 | if (C10_UNLIKELY(!is_left())) { |
104 | throw std::logic_error( |
105 | "Tried to get left side of an either which is right." ); |
106 | } |
107 | return _left; // NOLINT(cppcoreguidelines-pro-type-union-access) |
108 | } |
109 | Left& left() & { |
110 | return const_cast<Left&>( |
111 | const_cast<const either<Left, Right>*>(this)->left()); |
112 | } |
113 | Left&& left() && { |
114 | return std::move(left()); |
115 | } |
116 | |
117 | const Right& right() const& { |
118 | if (C10_UNLIKELY(!is_right())) { |
119 | throw std::logic_error( |
120 | "Tried to get right side of an either which is left." ); |
121 | } |
122 | return _right; // NOLINT(cppcoreguidelines-pro-type-union-access) |
123 | } |
124 | Right& right() & { |
125 | return const_cast<Right&>( |
126 | const_cast<const either<Left, Right>*>(this)->right()); |
127 | } |
128 | Right&& right() && { |
129 | return std::move(right()); |
130 | } |
131 | |
132 | template <class Result, class LeftFoldFunc, class RightFoldFunc> |
133 | Result fold(LeftFoldFunc&& leftFoldFunc, RightFoldFunc&& rightFoldFunc) |
134 | const { |
135 | if (Side::left == _side) { |
136 | return std::forward<LeftFoldFunc>(leftFoldFunc)(_left); |
137 | } else { |
138 | return std::forward<RightFoldFunc>(rightFoldFunc)(_right); |
139 | } |
140 | } |
141 | |
142 | private: |
143 | union { |
144 | Left _left; |
145 | Right _right; |
146 | }; |
147 | enum class Side : uint8_t { left, right } _side; |
148 | |
149 | explicit either(Side side) noexcept : _side(side) {} |
150 | |
151 | template <typename... Args> |
152 | void _construct_left(Args&&... args) { |
153 | new (&_left) Left(std::forward<Args>( |
154 | args)...); // NOLINT(cppcoreguidelines-pro-type-union-access) |
155 | } |
156 | template <typename... Args> |
157 | void _construct_right(Args&&... args) { |
158 | new (&_right) Right(std::forward<Args>( |
159 | args)...); // NOLINT(cppcoreguidelines-pro-type-union-access) |
160 | } |
161 | void _destruct() noexcept { |
162 | if (_side == Side::left) { |
163 | _left.~Left(); // NOLINT(cppcoreguidelines-pro-type-union-access) |
164 | } else { |
165 | _right.~Right(); // NOLINT(cppcoreguidelines-pro-type-union-access) |
166 | } |
167 | } |
168 | |
169 | template <typename Left_, typename Right_, typename... Args> |
170 | friend either<Left_, Right_> make_left(Args&&... args); |
171 | |
172 | template <typename Left_, typename Right_, typename... Args> |
173 | friend either<Left_, Right_> make_right(Args&&... args); |
174 | }; |
175 | |
176 | template <class Left, class Right> |
177 | inline bool operator==( |
178 | const either<Left, Right>& lhs, |
179 | const either<Left, Right>& rhs) { |
180 | if (lhs.is_left() != rhs.is_left()) { |
181 | return false; |
182 | } |
183 | if (lhs.is_left()) { |
184 | return lhs.left() == rhs.left(); |
185 | } else { |
186 | return lhs.right() == rhs.right(); |
187 | } |
188 | } |
189 | |
190 | template <class Left, class Right> |
191 | inline bool operator!=( |
192 | const either<Left, Right>& lhs, |
193 | const either<Left, Right>& rhs) { |
194 | return !operator==(lhs, rhs); |
195 | } |
196 | |
197 | template <class Left, class Right> |
198 | inline std::ostream& operator<<( |
199 | std::ostream& stream, |
200 | const either<Left, Right>& value) { |
201 | if (value.is_left()) { |
202 | stream << "Left(" << value.left() << ")" ; |
203 | } else { |
204 | stream << "Right(" << value.right() << ")" ; |
205 | } |
206 | return stream; |
207 | } |
208 | |
209 | template <typename Left, typename Right, typename... Args> |
210 | inline either<Left, Right> make_left(Args&&... args) { |
211 | either<Left, Right> result(either<Left, Right>::Side::left); |
212 | result._construct_left(std::forward<Args>(args)...); |
213 | return result; |
214 | } |
215 | |
216 | template <typename Left, typename Right, typename... Args> |
217 | inline either<Left, Right> make_right(Args&&... args) { |
218 | either<Left, Right> result(either<Left, Right>::Side::right); |
219 | result._construct_right(std::forward<Args>(args)...); |
220 | return result; |
221 | } |
222 | } // namespace c10 |
223 | |