Skip to content

Commit 2834fd9

Browse files
authored
feat: ✨ Result class (#63)
2 parents 00728bd + 92bdad1 commit 2834fd9

File tree

3 files changed

+386
-5
lines changed

3 files changed

+386
-5
lines changed

include/common/result.hpp

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
#pragma once
2+
3+
#include <chrono>
4+
#include <concepts>
5+
#include <limits>
6+
#include <optional>
7+
#include <stacktrace>
8+
#include <variant>
9+
10+
namespace zest {
11+
12+
/**
13+
* @brief Base class for custom error types used in the Result class.
14+
*
15+
*/
16+
class ResultError {
17+
public:
18+
/**
19+
* @brief struct containing data that can only be known at runtime.
20+
*
21+
*/
22+
struct RuntimeData {
23+
std::stacktrace stacktrace;
24+
std::chrono::time_point<std::chrono::system_clock> time;
25+
};
26+
27+
/**
28+
* @brief Construct a new ResultError object.
29+
*
30+
* @details Captures the current stacktrace and system time if called at runtime.
31+
*/
32+
constexpr ResultError() {
33+
if !consteval {
34+
runtime_data = {
35+
.stacktrace = std::stacktrace::current(),
36+
.time = std::chrono::system_clock::now()
37+
};
38+
}
39+
}
40+
41+
std::optional<RuntimeData> runtime_data;
42+
};
43+
44+
/**
45+
* @brief Trait to define a "sentinel" value for types indicating an error state.
46+
* @tparam T Type to provide a sentinel value for.
47+
* @note Specialize this template for custom types if needed.
48+
*/
49+
template<typename T>
50+
class SentinelValue;
51+
52+
/**
53+
* @brief Concept to check if a type has a defined sentinel value.
54+
* @tparam T Type to check.
55+
*/
56+
template<typename T>
57+
concept Sentinel = requires(const T& val) { SentinelValue<T>::value; };
58+
59+
/**
60+
* @brief Helper variable to simplify access to a type's sentinel value.
61+
* @tparam T Type with a defined sentinel (must satisfy Sentinel concept).
62+
*/
63+
template<Sentinel T>
64+
constexpr T sentinel_v = SentinelValue<T>::value;
65+
66+
/**
67+
* @brief Partial specialization of SentinelValue for integral and floating-point types.
68+
* @tparam T Integral or floating-point type.
69+
* @details Uses infinity for floating-point types if available; otherwise uses max value.
70+
*/
71+
template<typename T>
72+
requires(std::integral<T> || std::floating_point<T>)
73+
class SentinelValue<T> {
74+
public:
75+
static constexpr T get() {
76+
if constexpr (std::numeric_limits<T>::has_infinity) {
77+
return std::numeric_limits<T>::infinity();
78+
} else {
79+
return std::numeric_limits<T>::max();
80+
}
81+
}
82+
83+
static constexpr T value = get(); ///< Precomputed sentinel value for type T.
84+
};
85+
86+
/**
87+
* @brief Result class for expected value or error handling (similar to std::expected).
88+
* @tparam T Type of the expected value.
89+
* @tparam Errs List of possible error types (must inherit from ResultError).
90+
* @note Errors are stored in a variant, and the value is always initialized.
91+
*/
92+
template<typename T, typename... Errs>
93+
requires(sizeof...(Errs) > 0) && (std::derived_from<Errs, ResultError> && ...)
94+
class Result {
95+
public:
96+
/**
97+
* @brief Construct a Result with a normal value (no error).
98+
* @tparam U Type convertible to T.
99+
* @param value Value to initialize the result with.
100+
*/
101+
template<typename U>
102+
requires std::constructible_from<T, U>
103+
constexpr Result(U&& value)
104+
: error(std::monostate()),
105+
value(std::forward<U>(value)) {}
106+
107+
/**
108+
* @brief Construct a Result with a value and an error.
109+
* @tparam U Type convertible to T.
110+
* @tparam E Error type (must be in Errs).
111+
* @param value Value to store.
112+
* @param error Error to store.
113+
*/
114+
template<typename U, typename E>
115+
requires std::constructible_from<T, U>
116+
&& (std::same_as<std::remove_cvref_t<E>, Errs> || ...)
117+
constexpr Result(U&& value, E&& error)
118+
: value(std::forward<U>(value)),
119+
error(std::forward<E>(error)) {}
120+
121+
/**
122+
* @brief Construct a Result with an error, initializing the value to its sentinel.
123+
* @tparam E Error type (must be in Errs).
124+
* @param error Error to store.
125+
* @note Requires T to have a defined sentinel value (via SentinelValue<T>).
126+
*/
127+
template<typename E>
128+
requires Sentinel<T> && (std::same_as<std::remove_cvref_t<E>, Errs> || ...)
129+
constexpr Result(E&& error)
130+
: error(std::forward<E>(error)),
131+
value(sentinel_v<T>) {}
132+
133+
/**
134+
* @brief Get an error of type E if present (const-qualified overload).
135+
* @tparam E Error type to retrieve.
136+
* @return std::optional<E> Contains the error if present; otherwise nullopt.
137+
*/
138+
template<typename E>
139+
requires(std::same_as<E, Errs> || ...)
140+
constexpr std::optional<E> get() const& {
141+
if (std::holds_alternative<E>(error)) {
142+
return std::get<E>(error);
143+
} else {
144+
return std::nullopt;
145+
}
146+
}
147+
148+
/**
149+
* @brief Get an error of type E if present (rvalue overload).
150+
* @tparam E Error type to retrieve.
151+
* @return std::optional<E> Contains the error if present; otherwise nullopt.
152+
*/
153+
template<typename E>
154+
requires(std::same_as<E, Errs> || ...)
155+
constexpr std::optional<E> get() && {
156+
if (std::holds_alternative<E>(error)) {
157+
return std::move(std::get<E>(error));
158+
} else {
159+
return std::nullopt;
160+
}
161+
}
162+
163+
/**
164+
* @brief Get an error of type E if present (const rvalue overload).
165+
* @tparam E Error type to retrieve.
166+
* @return std::optional<E> Contains the error if present; otherwise nullopt.
167+
*/
168+
template<typename E>
169+
requires(std::same_as<E, Errs> || ...)
170+
constexpr const std::optional<E> get() const&& {
171+
if (std::holds_alternative<E>(error)) {
172+
return std::move(std::get<E>(error));
173+
} else {
174+
return std::nullopt;
175+
}
176+
}
177+
178+
/**
179+
* @brief Get the stored value (const-qualified overload).
180+
* @return T Copy of the stored value.
181+
*/
182+
template<typename U = T>
183+
requires std::same_as<U, T>
184+
constexpr T get() const& {
185+
return value;
186+
}
187+
188+
/**
189+
* @brief Get the stored value (rvalue overload).
190+
* @return T Moved value.
191+
*/
192+
template<typename U = T>
193+
requires std::same_as<U, T>
194+
constexpr T get() && {
195+
return std::move(value);
196+
}
197+
198+
constexpr operator T&() & {
199+
return value;
200+
}
201+
202+
constexpr operator const T&() const& {
203+
return value;
204+
};
205+
206+
constexpr operator T&&() && {
207+
return std::move(value);
208+
}
209+
210+
constexpr operator const T&&() const&& {
211+
return std::move(value);
212+
}
213+
214+
/**
215+
* @brief error value
216+
* @details instead of wrapping the variant in std::optional, it's more efficient to use
217+
* std::monostate. since we have to use std::variant in any case.
218+
*/
219+
std::variant<std::monostate, Errs...> error;
220+
T value;
221+
};
222+
223+
/**
224+
* @brief compare Result instances with comparable normal values
225+
*
226+
* @tparam LhsT the normal value type of the left-hand side argument
227+
* @tparam RhsT the normal value type of the right-hand side argument
228+
* @tparam LhsErrs the error value types of the left-hand side argument
229+
* @tparam RhsErrs the error value types of the right-hand side argument
230+
* @param lhs the left-hand side of the expression
231+
* @param rhs the right-hand side of the expression
232+
* @return true if the values are equal
233+
* @return false if the values are not equal
234+
*/
235+
template<typename LhsT, typename RhsT, typename... LhsErrs, typename... RhsErrs>
236+
requires std::equality_comparable_with<LhsT, RhsT>
237+
constexpr bool
238+
operator==(const Result<LhsT, LhsErrs...>& lhs, const Result<RhsT, RhsErrs...>& rhs) {
239+
return lhs.value == rhs.value;
240+
}
241+
242+
/**
243+
* @brief Result specialization for void value type (no stored value).
244+
* @tparam Errs List of possible error types (must inherit from ResultError).
245+
*/
246+
template<typename... Errs>
247+
requires(sizeof...(Errs) > 0) && (std::derived_from<Errs, ResultError> && ...)
248+
class Result<void, Errs...> {
249+
public:
250+
/**
251+
* @brief Construct a Result with an error.
252+
* @tparam E Error type (must be in Errs).
253+
* @param error Error to store.
254+
*/
255+
template<typename E>
256+
requires(std::same_as<std::remove_cvref_t<E>, Errs> || ...)
257+
constexpr Result(E&& error)
258+
: error(std::forward<E>(error)) {}
259+
260+
/**
261+
* @brief Construct a Result with no error (success state).
262+
*/
263+
constexpr Result()
264+
: error(std::monostate()) {}
265+
266+
/**
267+
* @brief Get an error of type E if present (const-qualified overload).
268+
* @tparam E Error type to retrieve.
269+
* @return std::optional<E> Contains the error if present; otherwise nullopt.
270+
*/
271+
template<typename E>
272+
requires(std::same_as<E, Errs> || ...)
273+
constexpr std::optional<E> get() const& {
274+
if (std::holds_alternative<E>(error)) {
275+
return std::get<E>(error);
276+
} else {
277+
return std::nullopt;
278+
}
279+
}
280+
281+
/**
282+
* @brief Get an error of type E if present (rvalue overload).
283+
* @tparam E Error type to retrieve.
284+
* @return std::optional<E> Contains the error if present; otherwise nullopt.
285+
*/
286+
template<typename E>
287+
requires(std::same_as<E, Errs> || ...)
288+
constexpr std::optional<E> get() && {
289+
if (std::holds_alternative<E>(error)) {
290+
return std::move(std::get<E>(error));
291+
} else {
292+
return std::nullopt;
293+
}
294+
}
295+
296+
/**
297+
* @brief Get an error of type E if present (const rvalue overload).
298+
* @tparam E Error type to retrieve.
299+
* @return std::optional<E> Contains the error if present; otherwise nullopt.
300+
*/
301+
template<typename E>
302+
requires(std::same_as<E, Errs> || ...)
303+
constexpr const std::optional<E> get() const&& {
304+
if (std::holds_alternative<E>(error)) {
305+
return std::move(std::get<E>(error));
306+
} else {
307+
return std::nullopt;
308+
}
309+
}
310+
311+
std::variant<std::monostate, Errs...> error; ///< Variant holding an error or monostate.
312+
};
313+
314+
} // namespace zest

meson.build

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ linker_flags = [
6464
# system libraries we depend on
6565
system_deps = [
6666
'-nostartfiles', # we still need to implement some newlib stubs
67+
'-lstdc++exp',
6768
]
6869
add_global_link_arguments( system_deps, language: 'c')
6970
add_global_link_arguments(system_deps, language: 'cpp')
@@ -73,11 +74,16 @@ stdlib_conf = [
7374
'-D_POSIX_MONOTONIC_CLOCK', # enable the POSIX monotonic clock
7475
]
7576

77+
# warning flags
78+
warning_flags = [
79+
'-Wno-psabi' # all libraries (except libv5) are compiled from source, making this warning useless
80+
]
81+
7682
# apply all these flags and configs
77-
add_global_arguments(optimization_flags, formatting_flags, stdlib_conf, language: 'c')
78-
add_global_arguments(optimization_flags, formatting_flags, stdlib_conf, language: 'cpp')
79-
add_global_link_arguments(optimization_flags, linker_flags, formatting_flags, system_deps, language: 'c')
80-
add_global_link_arguments(optimization_flags, linker_flags, formatting_flags, system_deps, language: 'cpp')
83+
add_global_arguments(optimization_flags, formatting_flags, warning_flags, stdlib_conf, language: 'c')
84+
add_global_arguments(optimization_flags, formatting_flags, warning_flags, stdlib_conf, language: 'cpp')
85+
add_global_link_arguments(optimization_flags, linker_flags, formatting_flags, warning_flags, system_deps, language: 'c')
86+
add_global_link_arguments(optimization_flags, linker_flags, formatting_flags, warning_flags, system_deps, language: 'cpp')
8187

8288
# include directories.
8389
# we only specify the top level in order to enforce paths in include directives.
@@ -109,4 +115,4 @@ custom_target(
109115
input: elf,
110116
build_by_default: true, # otherwise it won't be built
111117
command: [objcopy, ['-O', 'binary', '-S', '@INPUT@', '@OUTPUT@']],
112-
)
118+
)

0 commit comments

Comments
 (0)