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

Commit a567f25

Browse files
committed
significantly simplify Result::or_else constraints
1 parent bafcdf3 commit a567f25

1 file changed

Lines changed: 54 additions & 74 deletions

File tree

include/common/result.hpp

Lines changed: 54 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ inline constexpr bool is_result_v = is_result<T>::value;
202202
template<typename T>
203203
concept IsResult = is_result_v<T>;
204204

205+
// special type that is ignored by concepts like all_same
206+
struct ignored_type {};
207+
205208
/**
206209
* @brief Check whether all types in a pack are the same
207210
*
@@ -210,9 +213,6 @@ concept IsResult = is_result_v<T>;
210213
template<typename... Ts>
211214
struct all_same : std::true_type {};
212215

213-
// special type that is ignored by the all_same trait
214-
struct ignored_type {};
215-
216216
/**
217217
* @brief Check whether all types in a pack are the same
218218
*
@@ -223,13 +223,8 @@ template<typename T, typename... Ts>
223223
struct all_same<T, Ts...> :
224224
std::bool_constant<((std::is_same_v<T, Ts> || std::is_same_v<ignored_type, Ts>) && ...)> {};
225225

226-
/**
227-
* @brief Check whether all types in a pack are the same
228-
*
229-
* @tparam Ts types to check
230-
*/
231226
template<typename... Ts>
232-
inline constexpr bool all_same_v = all_same<Ts...>::value;
227+
concept AllSame = all_same<Ts...>::value;
233228

234229
// Primary template: no types, no result (causes substitution failure if used)
235230
template<typename Condition, typename... Ts>
@@ -260,6 +255,38 @@ struct invoke_result<F, Args...> : std::invoke_result<F, Args...> {};
260255
template<typename F, typename... Args>
261256
using invoke_result_t = invoke_result<F, Args...>::type;
262257

258+
// and_then return type helper
259+
template<typename Self, typename F>
260+
using and_then_return_t = std::invoke_result_t<F, decltype((std::declval<Self>().value))>;
261+
262+
// or_else return type helper
263+
template<typename Self, typename F, typename E>
264+
using or_else_return_t =
265+
traits::invoke_result_t<F, decltype((std::get<E>(std::declval<Self>().error)))>;
266+
267+
// or_else invocable callable helper
268+
template<typename Self, typename F, typename E>
269+
constexpr static bool invocable_v =
270+
std::is_invocable<F, decltype((std::get<E>(std::declval<Self>().error)))>::value;
271+
272+
template<typename F, typename Self>
273+
struct invocable_indirect_v {
274+
template<typename U>
275+
static constexpr bool value = invocable_v<Self, F, U>;
276+
};
277+
278+
template<typename F, typename Self, typename... Es>
279+
concept any_invocable = (traits::invocable_v<Self, F, Es> || ...);
280+
281+
template<typename T>
282+
concept ResultOrVoid =
283+
traits::IsResult<std::remove_cvref_t<T>> || std::same_as<void, std::remove_cvref_t<T>>
284+
|| std::same_as<ignored_type, T>;
285+
286+
template<typename R, typename T>
287+
concept ValueTypeMatch = std::same_as<void, T> || std::same_as<ignored_type, T>
288+
|| std::same_as<typename R::value_type, typename T::value_type>;
289+
263290
} // namespace traits
264291

265292
/**
@@ -271,27 +298,6 @@ using invoke_result_t = invoke_result<F, Args...>::type;
271298
template<typename T, traits::IsResultError... Errs>
272299
requires(traits::HasSentinel<T> || std::same_as<void, T>) && (sizeof...(Errs) > 0)
273300
class Result {
274-
private:
275-
// and_then return type helper
276-
template<typename Self, typename F>
277-
using and_then_return_t = std::invoke_result_t<F, decltype((std::declval<Self>().value))>;
278-
279-
// or_else return type helper
280-
template<typename Self, typename F, typename E>
281-
using or_else_return_t =
282-
traits::invoke_result_t<F, decltype((std::get<E>(std::declval<Self>().error)))>;
283-
284-
// or_else invocable callable helper
285-
template<typename Self, typename F, typename E>
286-
constexpr static bool or_else_invocable_v =
287-
std::is_invocable<F, decltype((std::get<E>(std::declval<Self>().error)))>::value;
288-
289-
template<typename F, typename Self>
290-
struct or_else_invocable_indirect_v {
291-
template<typename U>
292-
static constexpr bool value = or_else_invocable_v<Self, F, U>;
293-
};
294-
295301
public:
296302
// instead of wrapping the variant in std::optional, we can use std::monostate
297303
std::variant<std::monostate, Errs...> error;
@@ -378,13 +384,14 @@ class Result {
378384
*/
379385
template<typename Self, typename F>
380386
requires std::invocable<F, decltype((std::declval<Self>().value))>
381-
&& traits::is_result_v<and_then_return_t<Self, F>>
382-
&& traits::
383-
contains_all_v<typename and_then_return_t<Self, F>::error_types, error_types>
387+
&& traits::is_result_v<traits::and_then_return_t<Self, F>>
388+
&& traits::contains_all_v<
389+
typename traits::and_then_return_t<Self, F>::error_types,
390+
error_types>
384391
constexpr auto and_then(this Self&& self, F&& f) {
385392
// if there is an error, return said error immediately
386393
if (self.has_error()) {
387-
return std::visit([](auto&& arg) -> and_then_return_t<Self, F> {
394+
return std::visit([](auto&& arg) -> traits::and_then_return_t<Self, F> {
388395
// even though this conditional is impossible, it's necessary to stop the compiler
389396
// from thinking that ReturnType could be constructed with std::monostate
390397
if constexpr (std::is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
@@ -416,47 +423,21 @@ class Result {
416423
* @param f the callable
417424
*/
418425
template<typename Self, typename F>
419-
requires( // callable must be invocable with at least 1 error type
420-
or_else_invocable_v<Self, F, Errs> || ...
421-
)
422-
// the callable must always return the same type if it is invocable
423-
&& traits::all_same_v<std::conditional_t<
424-
or_else_invocable_v<Self, F, Errs>,
425-
// if the callable is invocable
426-
or_else_return_t<Self, F, Errs>,
427-
// if the callable is not invocable
428-
traits::ignored_type>...>
429-
// the callable must return a Result or void if it is invocable with a given error
430-
// type
431-
&& (std::conditional_t<
432-
or_else_invocable_v<Self, F, Errs>,
433-
// if the callable is invocable
434-
std::disjunction<
435-
traits::is_result<or_else_return_t<Self, F, Errs>>,
436-
std::is_same<or_else_return_t<Self, F, Errs>, void>>,
437-
// otherwise, if the callable is not invocable
438-
std::true_type>::value
439-
&& ...)
440-
// if F is invocable and returns a result, it must have the same value type as Self
441-
&& (std::conditional_t<
442-
or_else_invocable_v<Self, F, Errs>,
443-
// if the callable is invocable
444-
std::conditional_t<
445-
traits::is_result_v<or_else_return_t<Self, F, Errs>>,
446-
// if the return is a Result
447-
std::is_same<T, or_else_return_t<Self, F, Errs>>,
448-
// otherwise, if the return is not a Result
449-
std::true_type>,
450-
// otherwise, if the callable is not invocable
451-
std::true_type>::value
452-
&& ...)
426+
requires traits::any_invocable<F, Self, Errs...>
427+
// the callable must always return the same type (if it's invocable)
428+
&& traits::AllSame<traits::or_else_return_t<Self, F, Errs>...>
429+
// the callable must return a Result or void (if it's invocable)
430+
&& (traits::ResultOrVoid<traits::or_else_return_t<Self, F, Errs>> && ...)
431+
// if the callable returns a Result, it must have the same value type as this
432+
// Result instance
433+
&& (traits::ValueTypeMatch<Self, traits::or_else_return_t<Self, F, Errs>> && ...)
453434
constexpr auto or_else(this Self&& self, F&& f) {
454435
// the return type is the same as the return type of the callable, unless the callable
455436
// returns void. In that case, the return type is Self with cv-refs removed
456437
using CallableReturnType =
457-
traits::first_type_that_satisfies_t<or_else_invocable_indirect_v<F, Self>, Errs...>;
438+
traits::first_type_that_satisfies_t<traits::invocable_indirect_v<F, Self>, Errs...>;
458439
using ReturnType = std::conditional_t<
459-
traits::is_result_v<CallableReturnType>,
440+
traits::IsResult<CallableReturnType>,
460441
CallableReturnType,
461442
std::remove_cvref_t<Self>>;
462443

@@ -470,7 +451,7 @@ class Result {
470451
// even though this condition is impossible, it's necessary. Otherwise the
471452
// compiler will compile a branch where f is invoked with an unsupported argument
472453
// type
473-
if constexpr (!or_else_invocable_v<Self, F, decltype((arg))>
454+
if constexpr (!traits::invocable_v<Self, F, decltype((arg))>
474455
|| std::
475456
is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
476457
throw std::logic_error("This exception is unreachable");
@@ -484,7 +465,7 @@ class Result {
484465
// even though this condition is impossible, it's necessary. Otherwise the
485466
// compiler will compile a branch where f is invoked with an unsupported argument
486467
// type
487-
if constexpr (!or_else_invocable_v<Self, F, decltype((arg))>
468+
if constexpr (!traits::invocable_v<Self, F, decltype((arg))>
488469
|| std::is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
489470
throw std::logic_error("This exception is unreachable");
490471
} else {
@@ -644,7 +625,7 @@ class Result<void, Errs...> {
644625
template<typename Self, typename F>
645626
requires(std::invocable<F, decltype((std::get<Errs>(std::declval<Self>().error)))> && ...)
646627
&& (traits::is_result_v<or_else_return_t<Self, F, Errs>> && ...)
647-
&& (traits::all_same_v<or_else_return_t<Self, F, Errs>...>)
628+
&& (traits::AllSame<or_else_return_t<Self, F, Errs>...>)
648629
&& (std::same_as<void, typename or_else_return_t<Self, F, Errs>::value_type> && ...)
649630
constexpr auto or_else(this Self&& self, F&& f) {
650631
using ReturnType = or_else_return_t<Self, F, std::tuple_element_t<0, std::tuple<Errs...>>>;
@@ -662,5 +643,4 @@ class Result<void, Errs...> {
662643
}, std::forward<Self>(self).error);
663644
}
664645
};
665-
666646
} // namespace zest

0 commit comments

Comments
 (0)