Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
result.hpp
Go to the documentation of this file.
1#pragma once
2
19
20#include <cstddef>
21#include <new>
22#include <type_traits>
23#include <utility>
24
25namespace pulp::runtime {
26
27template <typename E>
28class Err;
29
30namespace detail {
31
32template <typename T>
33struct OkTag {};
34template <typename E>
35struct ErrTag {};
36
37} // namespace detail
38
41template <typename T>
42class Ok {
43public:
44 explicit Ok(T value) : value_(std::move(value)) {}
45 T& value() & { return value_; }
46 const T& value() const & { return value_; }
47 T&& value() && { return std::move(value_); }
48private:
49 T value_;
50};
51
52template <typename T>
53Ok(T) -> Ok<T>;
54
57template <typename E>
58class Err {
59public:
60 explicit Err(E value) : value_(std::move(value)) {}
61 E& value() & { return value_; }
62 const E& value() const & { return value_; }
63 E&& value() && { return std::move(value_); }
64private:
65 E value_;
66};
67
68template <typename E>
69Err(E) -> Err<E>;
70
75template <typename T, typename E>
76class Result {
77 static_assert(!std::is_same_v<T, E>, "Result<T,E> requires distinct T and E");
78
79public:
80 using value_type = T;
81 using error_type = E;
82
83 // Default-constructed Result is a value-initialized T (matches
84 // std::expected's default behavior when T is default-constructible).
86 requires std::is_default_constructible_v<T>
87 : has_value_(true) {
88 new (&storage_.value) T();
89 }
90
92 : has_value_(true) {
93 new (&storage_.value) T(std::move(ok).value());
94 }
95
97 : has_value_(false) {
98 new (&storage_.error) E(std::move(err).value());
99 }
100
101 Result(const Result& other) : has_value_(other.has_value_) {
102 if (has_value_) new (&storage_.value) T(other.storage_.value);
103 else new (&storage_.error) E(other.storage_.error);
104 }
105
106 Result(Result&& other) noexcept(std::is_nothrow_move_constructible_v<T> &&
107 std::is_nothrow_move_constructible_v<E>)
108 : has_value_(other.has_value_) {
109 if (has_value_) new (&storage_.value) T(std::move(other.storage_.value));
110 else new (&storage_.error) E(std::move(other.storage_.error));
111 }
112
113 // Strong exception guarantee. If T's or E's copy/move ctor throws,
114 // the destination is left holding its previous value — the old
115 // member is only destroyed AFTER the new one is fully constructed.
117 if (this != &other) {
118 if (other.has_value_) {
119 T tmp(other.storage_.value); // may throw, *this untouched
120 destroy();
121 has_value_ = true;
122 new (&storage_.value) T(std::move(tmp));
123 } else {
124 E tmp(other.storage_.error);
125 destroy();
126 has_value_ = false;
127 new (&storage_.error) E(std::move(tmp));
128 }
129 }
130 return *this;
131 }
132
134 std::is_nothrow_move_constructible_v<T> &&
135 std::is_nothrow_move_constructible_v<E>) {
136 if (this != &other) {
137 if (other.has_value_) {
138 T tmp(std::move(other.storage_.value));
139 destroy();
140 has_value_ = true;
141 new (&storage_.value) T(std::move(tmp));
142 } else {
143 E tmp(std::move(other.storage_.error));
144 destroy();
145 has_value_ = false;
146 new (&storage_.error) E(std::move(tmp));
147 }
148 }
149 return *this;
150 }
151
152 ~Result() { destroy(); }
153
154 // ── Inspection ──────────────────────────────────────────────────────
155
156 bool has_value() const noexcept { return has_value_; }
157 explicit operator bool() const noexcept { return has_value_; }
158 bool is_err() const noexcept { return !has_value_; }
159
160 // ── Value access ────────────────────────────────────────────────────
161 //
162 // Pre: `has_value()`. Calling these on an error result is UB — the
163 // caller is expected to check `has_value()` / `operator bool()`
164 // first, matching `std::expected`'s `operator*` contract.
165
166 T& value() & { return storage_.value; }
167 const T& value() const & { return storage_.value; }
168 T&& value() && { return std::move(storage_.value); }
169
170 T& operator*() & { return storage_.value; }
171 const T& operator*() const & { return storage_.value; }
172 T* operator->() { return &storage_.value; }
173 const T* operator->() const { return &storage_.value; }
174
176 template <typename U>
177 T value_or(U&& fallback) const & {
178 return has_value_ ? storage_.value
179 : static_cast<T>(std::forward<U>(fallback));
180 }
181
182 // ── Error access ────────────────────────────────────────────────────
183 //
184 // Pre: `!has_value()`.
185
186 E& error() & { return storage_.error; }
187 const E& error() const & { return storage_.error; }
188 E&& error() && { return std::move(storage_.error); }
189
190private:
191 union Storage {
192 Storage() {}
193 ~Storage() {}
194 T value;
195 E error;
196 } storage_;
197 bool has_value_;
198
199 void destroy() noexcept {
200 if (has_value_) storage_.value.~T();
201 else storage_.error.~E();
202 }
203};
204
205
216template <typename E>
217class Result<void, E> {
218public:
219 using value_type = void;
220 using error_type = E;
221
223 Result() noexcept : has_value_(true) {}
224
225 Result(Err<E> err) : has_value_(false) {
226 new (&error_) E(std::move(err).value());
227 }
228
229 Result(const Result& other) : has_value_(other.has_value_) {
230 if (!has_value_) new (&error_) E(other.error_ref());
231 }
232
233 Result(Result&& other) noexcept(std::is_nothrow_move_constructible_v<E>)
234 : has_value_(other.has_value_) {
235 if (!has_value_) new (&error_) E(std::move(other.error_ref()));
236 }
237
238 // Strong exception guarantee, matching the primary template: the old error
239 // is destroyed only after the new one is fully constructed.
241 if (this != &other) {
242 if (other.has_value_) {
243 destroy();
244 has_value_ = true;
245 } else {
246 E tmp(other.error_ref());
247 destroy();
248 has_value_ = false;
249 new (&error_) E(std::move(tmp));
250 }
251 }
252 return *this;
253 }
254
255 Result& operator=(Result&& other) noexcept(std::is_nothrow_move_constructible_v<E>) {
256 if (this != &other) {
257 if (other.has_value_) {
258 destroy();
259 has_value_ = true;
260 } else {
261 E tmp(std::move(other.error_ref()));
262 destroy();
263 has_value_ = false;
264 new (&error_) E(std::move(tmp));
265 }
266 }
267 return *this;
268 }
269
270 ~Result() { destroy(); }
271
272 bool has_value() const noexcept { return has_value_; }
273 explicit operator bool() const noexcept { return has_value_; }
274 bool is_err() const noexcept { return !has_value_; }
275
279 void value() const noexcept {}
280
281 // Pre: `!has_value()`.
282 E& error() & { return error_ref(); }
283 const E& error() const & { return error_ref(); }
284 E&& error() && { return std::move(error_ref()); }
285
286private:
287 union {
288 // Only ever alive while `has_value_` is false. An anonymous union keeps
289 // the object empty-ish in the success case rather than paying for an
290 // always-constructed E.
292 };
293 bool has_value_;
294
295 E& error_ref() noexcept { return error_; }
296 const E& error_ref() const noexcept { return error_; }
297
298 void destroy() noexcept {
299 if (!has_value_) error_.~E();
300 }
301};
302
303} // namespace pulp::runtime
Wrap a value as a failure result.
Definition result.hpp:58
Err(E value)
Definition result.hpp:60
E & value() &
Definition result.hpp:61
const E & value() const &
Definition result.hpp:62
E && value() &&
Definition result.hpp:63
Wrap a value as a success result.
Definition result.hpp:42
T && value() &&
Definition result.hpp:47
Ok(T value)
Definition result.hpp:44
T & value() &
Definition result.hpp:45
const T & value() const &
Definition result.hpp:46
Result(const Result &other)
Definition result.hpp:229
Result(Result &&other) noexcept(std::is_nothrow_move_constructible_v< E >)
Definition result.hpp:233
Result(Err< E > err)
Definition result.hpp:225
E error_type
Definition result.hpp:220
E error_
Definition result.hpp:291
E & error() &
Definition result.hpp:282
bool has_value() const noexcept
Definition result.hpp:272
~Result()
Definition result.hpp:270
void value() const noexcept
Present so generic code can call value() uniformly.
Definition result.hpp:279
void value_type
Definition result.hpp:219
E && error() &&
Definition result.hpp:284
Result & operator=(const Result &other)
Definition result.hpp:240
Result & operator=(Result &&other) noexcept(std::is_nothrow_move_constructible_v< E >)
Definition result.hpp:255
bool is_err() const noexcept
Definition result.hpp:274
const E & error() const &
Definition result.hpp:283
Result() noexcept
Success.
Definition result.hpp:223
bool is_err() const noexcept
Definition result.hpp:158
Result(Err< E > err)
Definition result.hpp:96
const E & error() const &
Definition result.hpp:187
const T & value() const &
Definition result.hpp:167
Result()
Definition result.hpp:85
Result(Ok< T > ok)
Definition result.hpp:91
Result(const Result &other)
Definition result.hpp:101
Result & operator=(Result &&other) noexcept(std::is_nothrow_move_constructible_v< T > &&std::is_nothrow_move_constructible_v< E >)
Definition result.hpp:133
Result(Result &&other) noexcept(std::is_nothrow_move_constructible_v< T > &&std::is_nothrow_move_constructible_v< E >)
Definition result.hpp:106
Result & operator=(const Result &other)
Definition result.hpp:116
E & error() &
Definition result.hpp:186
T && value() &&
Definition result.hpp:168
~Result()
Definition result.hpp:152
T * operator->()
Definition result.hpp:172
const T * operator->() const
Definition result.hpp:173
T & operator*() &
Definition result.hpp:170
T & value() &
Definition result.hpp:166
bool has_value() const noexcept
Definition result.hpp:156
E && error() &&
Definition result.hpp:188
T value_or(U &&fallback) const &
Return the held value, or fallback when this is an error.
Definition result.hpp:177
const T & operator*() const &
Definition result.hpp:171
Definition assert.hpp:29
Definition generated_event_source.hpp:33
Err(E) -> Err< E >
Ok(T) -> Ok< T >
@ other
Definition udev_monitor.hpp:26
STL namespace.
Definition result.hpp:35
Definition result.hpp:33