Skip to content

Commit 2844c9d

Browse files
committed
add and_then monadic function to Result class
1 parent 0ddcf83 commit 2844c9d

File tree

1 file changed

+86
-1
lines changed

1 file changed

+86
-1
lines changed

include/common/result.hpp

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
#include <chrono>
44
#include <concepts>
5+
#include <functional>
56
#include <limits>
67
#include <optional>
78
#include <stacktrace>
9+
#include <type_traits>
810
#include <variant>
911

1012
namespace zest {
@@ -53,6 +55,48 @@ namespace traits {
5355
template<typename T, typename... Ts>
5456
inline constexpr bool is_in_pack_v = (std::is_same_v<std::remove_cvref_t<T>, Ts> || ...);
5557

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+
56100
/**
57101
* @brief type trait for checking whether a type is derived from the ResultError class
58102
*/
@@ -170,6 +214,9 @@ template<typename T, traits::IsResultError... Errs>
170214
requires(sizeof...(Errs) > 0)
171215
class Result {
172216
public:
217+
using value_type = T;
218+
using error_types = traits::type_pack<Errs...>;
219+
173220
/**
174221
* @brief Construct a Result with a normal value (no error).
175222
* @tparam U Type convertible to T, and U not derived from ResultError
@@ -225,6 +272,44 @@ class Result {
225272
}
226273
}
227274

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+
228313
/**
229314
* @brief Get the normal value
230315
*
@@ -267,7 +352,7 @@ class Result {
267352
// instead of wrapping the variant in std::optional, we can use std::monostate
268353
std::variant<std::monostate, Errs...> m_error;
269354
T m_value;
270-
};
355+
}; // namespace zest
271356

272357
/**
273358
* @brief compare Result instances with comparable normal values

0 commit comments

Comments
 (0)