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
0 commit comments