Skip to content
This repository was archived by the owner on May 3, 2026. It is now read-only.

Commit 658d9f6

Browse files
committed
add Result::or_else monadic function
1 parent faccaec commit 658d9f6

1 file changed

Lines changed: 132 additions & 18 deletions

File tree

include/common/result.hpp

Lines changed: 132 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,36 @@ inline constexpr bool is_result_v = is_result<T>::value;
197197

198198
/**
199199
* @brief Concept to check if a type is a Result
200-
*
201-
* @tparam T
200+
* @tparam T Type to check
202201
*/
203202
template<typename T>
204203
concept IsResult = is_result_v<T>;
205204

205+
/**
206+
* @brief Check whether all types in a pack are the same
207+
*
208+
* @tparam Ts types to check
209+
*/
210+
template<typename... Ts>
211+
struct all_same : std::true_type {};
212+
213+
/**
214+
* @brief Check whether all types in a pack are the same
215+
*
216+
* @tparam T first type
217+
* @tparam Ts the rest of the types
218+
*/
219+
template<typename T, typename... Ts>
220+
struct all_same<T, Ts...> : std::bool_constant<(std::is_same_v<T, Ts> && ...)> {};
221+
222+
/**
223+
* @brief Check whether all types in a pack are the same
224+
*
225+
* @tparam Ts types to check
226+
*/
227+
template<typename... Ts>
228+
inline constexpr bool all_same_v = all_same<Ts...>::value;
229+
206230
} // namespace traits
207231

208232
/**
@@ -215,11 +239,15 @@ template<typename T, traits::IsResultError... Errs>
215239
requires(traits::HasSentinel<T> || std::same_as<void, T>) && (sizeof...(Errs) > 0)
216240
class Result {
217241
private:
218-
// helper type
242+
// and_then return type helper
219243
template<typename Self, typename F>
220-
requires std::invocable<F, decltype((std::declval<Self>().value))>
221244
using and_then_return_t = std::invoke_result_t<F, decltype((std::declval<Self>().value))>;
222245

246+
// or_else return type helper
247+
template<typename Self, typename F, typename E>
248+
using or_else_return_t =
249+
std::invoke_result_t<F, decltype((std::get<E>(std::declval<Self>().error)))>;
250+
223251
public:
224252
// instead of wrapping the variant in std::optional, we can use std::monostate
225253
std::variant<std::monostate, Errs...> error;
@@ -233,8 +261,8 @@ class Result {
233261
* @tparam U Type convertible to T, and U not derived from ResultError
234262
* @param value Value to initialize the result with.
235263
*/
236-
template<typename U>
237-
requires std::convertible_to<T, U> && (!traits::is_in_pack_v<U, std::monostate, Errs...>)
264+
template<traits::IsResultError U>
265+
requires std::convertible_to<T, U>
238266
constexpr Result(U&& value)
239267
: error(std::monostate()),
240268
value(std::forward<U>(value)) {}
@@ -245,7 +273,7 @@ class Result {
245273
* @param error Error to store.
246274
* @note Requires T to have a defined sentinel value (via SentinelValue<T>).
247275
*/
248-
template<typename E>
276+
template<traits::IsResultError E>
249277
requires traits::is_in_pack_v<E, Errs...>
250278
constexpr Result(E&& error)
251279
: error(std::forward<E>(error)),
@@ -305,22 +333,63 @@ class Result {
305333
* @return return type of callable
306334
*/
307335
template<typename Self, typename F>
308-
constexpr auto and_then(this Self&& self, F&& f)
309-
requires std::invocable<F, decltype(std::forward<Self>(self).value)>
336+
requires std::invocable<F, decltype((std::declval<Self>().value))>
310337
&& traits::is_result_v<and_then_return_t<Self, F>>
311338
&& traits::
312339
contains_all_v<typename and_then_return_t<Self, F>::error_types, error_types>
313-
{
340+
constexpr auto and_then(this Self&& self, F&& f) {
314341
// if there is an error, return said error immediately
315342
if (self.has_error()) {
316-
return std::visit([](auto&& var) -> and_then_return_t<Self, F> {
317-
return std::forward<decltype(var)>(var);
343+
return std::visit([](auto&& arg) -> and_then_return_t<Self, F> {
344+
// even though this conditional is impossible, it's necessary to stop the compiler
345+
// from thinking that ReturnType could be constructed with std::monostate
346+
if constexpr (std::is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
347+
throw std::logic_error("This exception is unreachable");
348+
} else {
349+
return std::forward<decltype(arg)>(arg);
350+
}
318351
}, std::forward<Self>(self).error);
319352
}
320353
// otherwise, invoke the callable and return the result
321354
return std::invoke(f, std::forward<Self>(self).value);
322355
}
323356

357+
/**
358+
* @brief or_else monadic function.
359+
*
360+
* The callable must be able to take a perfectly forwarded error that may be contained by this
361+
* Result instance. The callable must be able to return a Result containing any error type that
362+
* may be passed to it. The normal value type of the Result returned by the callable must be the
363+
* same as the normal value type of this Result instance.
364+
*
365+
* @tparam Self deduced self type
366+
* @tparam F the type of the callable
367+
* @param self the current Result instance
368+
* @param f the callable
369+
*/
370+
template<typename Self, typename F>
371+
requires(std::invocable<F, decltype((std::get<Errs>(std::declval<Self>().error)))> && ...)
372+
&& (traits::is_result_v<or_else_return_t<Self, F, Errs>> && ...)
373+
&& (traits::all_same_v<or_else_return_t<Self, F, Errs>...>)
374+
&& (std::same_as<T, typename or_else_return_t<Self, F, Errs>::value_type> && ...)
375+
constexpr auto or_else(this Self&& self, F&& f) {
376+
using ReturnType = or_else_return_t<Self, F, std::tuple_element_t<1, std::tuple<Errs...>>>;
377+
// if there isn't an error, return
378+
if (!self.has_error()) {
379+
return ReturnType(std::forward<Self>(self).value);
380+
}
381+
// otherwise, invoke the given lambda
382+
return std::visit([&f](auto&& arg) -> ReturnType {
383+
// even though this conditional is impossible, it's necessary to stop the compiler from
384+
// thinking that ReturnType could be constructed with std::monostate
385+
if constexpr (std::is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
386+
throw std::logic_error("This exception is unreachable");
387+
} else {
388+
return std::invoke(f, arg);
389+
}
390+
}, std::forward<Self>(self).error);
391+
}
392+
324393
constexpr operator T&() & {
325394
return value;
326395
}
@@ -364,6 +433,12 @@ operator==(const Result<LhsT, LhsErrs...>& lhs, const Result<RhsT, RhsErrs...>&
364433
template<traits::IsResultError... Errs>
365434
requires(sizeof...(Errs) > 0)
366435
class Result<void, Errs...> {
436+
private:
437+
// or_else return type helper
438+
template<typename Self, typename F, typename E>
439+
using or_else_return_t =
440+
std::invoke_result_t<F, decltype((std::get<E>(std::declval<Self>().error)))>;
441+
367442
public:
368443
using value_type = void;
369444
using error_types = traits::type_pack<Errs...>;
@@ -376,8 +451,8 @@ class Result<void, Errs...> {
376451
* @tparam E Error type (must be in Errs).
377452
* @param error Error to store.
378453
*/
379-
template<typename E>
380-
requires traits::is_in_pack_v<E, std::monostate, Errs...>
454+
template<traits::IsResultError E>
455+
requires traits::is_in_pack_v<E, Errs...>
381456
constexpr Result(E&& error)
382457
: error(std::forward<E>(error)) {}
383458

@@ -429,20 +504,59 @@ class Result<void, Errs...> {
429504
* @return return type of callable
430505
*/
431506
template<std::invocable F, typename Self>
432-
constexpr auto and_then(this Self&& self, F&& f)
433507
requires traits::is_result_v<std::invoke_result_t<F>>
434508
&& traits::
435509
contains_all_v<typename std::invoke_result_t<F>::error_types, error_types>
436-
{
510+
constexpr auto and_then(this Self&& self, F&& f) {
437511
// if there is an error, return said error immediately
438512
if (self.has_error()) {
439-
return std::visit([](auto&& var) -> std::invoke_result_t<F> {
440-
return std::forward<decltype(var)>(var);
513+
return std::visit([](auto&& arg) -> std::invoke_result_t<F> {
514+
// even though this conditional is impossible, it's necessary to stop the compiler
515+
// from thinking that ReturnType could be constructed with std::monostate
516+
if constexpr (std::is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
517+
throw std::logic_error("This exception is unreachable");
518+
} else {
519+
return std::forward<decltype(arg)>(arg);
520+
}
441521
}, std::forward<Self>(self).error);
442522
}
443523
// otherwise, invoke the callable and return the result
444524
return std::invoke(f);
445525
}
526+
527+
/**
528+
* @brief or_else monadic function.
529+
*
530+
* The callable must be able to take a perfectly forwarded error that may be contained by this
531+
* Result instance. The callable must be able to return a Result containing any error type that
532+
* may be passed to it. The normal value type of the Result returned by the callable must be the
533+
* same as the normal value type of this Result instance.
534+
*
535+
* @tparam Self deduced self type
536+
* @tparam F the type of the callable
537+
* @param self the current Result instance
538+
* @param f the callable
539+
*/
540+
template<typename Self, typename F>
541+
requires(std::invocable<F, decltype((std::get<Errs>(std::declval<Self>().error)))> && ...)
542+
&& (traits::is_result_v<or_else_return_t<Self, F, Errs>> && ...)
543+
&& (traits::all_same_v<or_else_return_t<Self, F, Errs>...>)
544+
&& (std::same_as<void, typename or_else_return_t<Self, F, Errs>::value_type> && ...)
545+
constexpr auto or_else(this Self&& self, F&& f) {
546+
using ReturnType = or_else_return_t<Self, F, std::tuple_element_t<1, std::tuple<Errs...>>>;
547+
// if there isn't an error, return
548+
return ReturnType();
549+
// otherwise, invoke the given lambda
550+
return std::visit([&f](auto&& arg) -> ReturnType {
551+
// even though this conditional is impossible, it's necessary to stop the compiler from
552+
// thinking that ReturnType could be constructed with std::monostate
553+
if constexpr (std::is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
554+
throw std::logic_error("This exception is unreachable");
555+
} else {
556+
return std::invoke(f, arg);
557+
}
558+
}, std::forward<Self>(self).error);
559+
}
446560
};
447561

448562
} // namespace zest

0 commit comments

Comments
 (0)