|
2 | 2 |
|
3 | 3 | #include <chrono> |
4 | 4 | #include <concepts> |
| 5 | +#include <functional> |
5 | 6 | #include <limits> |
6 | 7 | #include <optional> |
7 | 8 | #include <stacktrace> |
| 9 | +#include <type_traits> |
8 | 10 | #include <variant> |
9 | 11 |
|
10 | 12 | namespace zest { |
@@ -53,6 +55,48 @@ namespace traits { |
53 | 55 | template<typename T, typename... Ts> |
54 | 56 | inline constexpr bool is_in_pack_v = (std::is_same_v<std::remove_cvref_t<T>, Ts> || ...); |
55 | 57 |
|
| 58 | +// A simple type pack wrapper |
| 59 | +template<typename... Ts> |
| 60 | +struct type_pack {}; |
| 61 | + |
| 62 | +/** |
| 63 | + * @brief Check if PackA contains all the types in PackB |
| 64 | + * |
| 65 | + * @tparam PackA |
| 66 | + * @tparam PackB |
| 67 | + */ |
| 68 | +template<typename PackA, typename PackB> |
| 69 | +struct contains_all; |
| 70 | + |
| 71 | +/** |
| 72 | + * @brief Check if PackA contains all the types in PackB |
| 73 | + * |
| 74 | + * @tparam PackA |
| 75 | + * @tparam PackB |
| 76 | + */ |
| 77 | +template<typename... As, typename... Bs> |
| 78 | +struct contains_all<type_pack<As...>, type_pack<Bs...>> { |
| 79 | + static constexpr bool value = (is_in_pack_v<Bs, As...> && ...); |
| 80 | +}; |
| 81 | + |
| 82 | +/** |
| 83 | + * @brief Check if PackA contains all the types in PackB |
| 84 | + * |
| 85 | + * @tparam PackA |
| 86 | + * @tparam PackB |
| 87 | + */ |
| 88 | +template<typename PackA, typename PackB> |
| 89 | +inline constexpr bool contains_all_v = contains_all<PackA, PackB>::value; |
| 90 | + |
| 91 | +/** |
| 92 | + * @brief Check if PackA contains all the types in PackB |
| 93 | + * |
| 94 | + * @tparam PackA |
| 95 | + * @tparam PackB |
| 96 | + */ |
| 97 | +template<typename PackA, typename PackB> |
| 98 | +concept ContainsAll = contains_all_v<PackA, PackB>; |
| 99 | + |
56 | 100 | /** |
57 | 101 | * @brief type trait for checking whether a type is derived from the ResultError class |
58 | 102 | */ |
@@ -170,6 +214,9 @@ template<typename T, traits::IsResultError... Errs> |
170 | 214 | requires(sizeof...(Errs) > 0) |
171 | 215 | class Result { |
172 | 216 | public: |
| 217 | + using value_type = T; |
| 218 | + using error_types = traits::type_pack<Errs...>; |
| 219 | + |
173 | 220 | /** |
174 | 221 | * @brief Construct a Result with a normal value (no error). |
175 | 222 | * @tparam U Type convertible to T, and U not derived from ResultError |
@@ -225,6 +272,44 @@ class Result { |
225 | 272 | } |
226 | 273 | } |
227 | 274 |
|
| 275 | + /** |
| 276 | + * @brief Applies a callable to the stored value if no error is present, returning its result. |
| 277 | + * |
| 278 | + * This function implements a monadic bind operation (commonly known as `and_then` in functional |
| 279 | + * programming). If the `Result` object has a value, the callable `f` is invoked with the value, |
| 280 | + * and its result is returned. If the `Result` object has an error, the original object is |
| 281 | + * returned unmodified. |
| 282 | + * |
| 283 | + * The callable `f` must return a `Result` type that: |
| 284 | + * 1. May change the value type. |
| 285 | + * 2. Must contain all the error types from the original `Result`. |
| 286 | + * |
| 287 | + * @tparam Self A type that models a `Result`-like object, providing `get_value()`, |
| 288 | + * `has_error()`, and `error_types`. |
| 289 | + * @tparam F A callable type that can be invoked with the value from `self`, returning a |
| 290 | + * compatible `Result`. |
| 291 | + * @param self The source `Result` object, passed using `deduced this` for support of |
| 292 | + * rvalue/lvalue overloads. |
| 293 | + * @param f A callable to apply to the value if no error is present. |
| 294 | + * @return A `Result` returned from invoking `f(self.get_value())` if there is no error, or the |
| 295 | + * original `self` otherwise. |
| 296 | + * |
| 297 | + * @note Requires that `std::invoke_result_t<F, decltype(self.get_value())>` is a `Result` type |
| 298 | + * and contains all error types from `Self`. |
| 299 | + */ |
| 300 | + template<typename Self, std::invocable F> |
| 301 | + constexpr auto and_then(this Self&& self, F&& f) |
| 302 | + requires traits::is_result_v<std::invoke_result_t<F, decltype(self.get_value())>> |
| 303 | + && traits::contains_all_v< |
| 304 | + std::invoke_result_t<F, decltype(self.get_value())>, |
| 305 | + typename Self::error_types> |
| 306 | + { |
| 307 | + if (self.has_error()) { |
| 308 | + return std::forward<Self>(self); |
| 309 | + } |
| 310 | + return std::invoke(f, std::forward<Self>(self).get_value()); |
| 311 | + } |
| 312 | + |
228 | 313 | /** |
229 | 314 | * @brief Get the normal value |
230 | 315 | * |
@@ -267,7 +352,7 @@ class Result { |
267 | 352 | // instead of wrapping the variant in std::optional, we can use std::monostate |
268 | 353 | std::variant<std::monostate, Errs...> m_error; |
269 | 354 | T m_value; |
270 | | -}; |
| 355 | +}; // namespace zest |
271 | 356 |
|
272 | 357 | /** |
273 | 358 | * @brief compare Result instances with comparable normal values |
|
0 commit comments