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

Commit 45e44e7

Browse files
committed
add Result::inspect_error
1 parent d5b8469 commit 45e44e7

2 files changed

Lines changed: 101 additions & 8 deletions

File tree

include/common/result.hpp

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include "common/result.hpp"
34
#define I_WANT_RESULT_IMPL
45
#include "common/result_impl.hpp"
56
#undef I_WANT_RESULT_IMPL
@@ -165,8 +166,7 @@ class Result<T, Errs...> {
165166

166167
return std::visit([&f](auto&& arg) -> ReturnType {
167168
// even though this condition is impossible, it's necessary. Otherwise the
168-
// compiler will compile a branch where f is invoked with an unsupported argument
169-
// type
169+
// compiler will compile a branch where f is invoked with std::monostate
170170
if constexpr (std::same_as<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
171171
throw std::logic_error("This exception is unreachable");
172172
} else {
@@ -175,6 +175,39 @@ class Result<T, Errs...> {
175175
}, std::forward<Self>(self).error);
176176
}
177177

178+
/**
179+
* @brief invoke a callable with the error, if present
180+
*
181+
* constraints:
182+
* - Callable must be invocable with at least 1 of the possible error types
183+
* - Callable must return void, if invocable
184+
*
185+
* @tparam Self deduced self type
186+
* @tparam F the type of the callable
187+
* @param self the current Result instance
188+
* @param f the callable
189+
*/
190+
template<typename Self, typename F>
191+
requires traits::AnyInvocable<Self, F, Errs...>
192+
&& traits::AllVoid<traits::inspect_error_return_t<Self, F, Errs>...>
193+
constexpr void inspect_error(this Self&& self, F&& f) {
194+
// if there's no error contained, return
195+
if (!self.has_error()) {
196+
return;
197+
}
198+
// otherwise, invoke the callable
199+
std::visit([&f](auto&& arg) {
200+
// even though this condition is impossible, it's necessary. Otherwise the
201+
// compiler will compile a branch where f is invoked with an unsupported argument type
202+
if constexpr (std::same_as<std::monostate, std::remove_cvref_t<decltype(arg)>>
203+
|| !traits::Invocable<Self, F, std::remove_cvref_t<decltype(arg)>>) {
204+
throw std::logic_error("This exception is unreachable");
205+
} else {
206+
std::invoke(f, std::forward<decltype(arg)>(arg));
207+
}
208+
}, std::forward<Self>(self).error);
209+
}
210+
178211
constexpr operator T&() & {
179212
return value;
180213
}
@@ -336,14 +369,46 @@ class Result<void, Errs...> {
336369

337370
return std::visit([&f](auto&& arg) -> ReturnType {
338371
// even though this condition is impossible, it's necessary. Otherwise the
339-
// compiler will compile a branch where f is invoked with an unsupported argument
340-
// type
372+
// compiler will compile a branch where f is invoked with std::monostate
341373
if constexpr (std::same_as<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
342374
throw std::logic_error("This exception is unreachable");
343375
} else {
344376
return std::invoke(f, std::forward<decltype(arg)>(arg));
345377
}
346378
}, std::forward<Self>(self).error);
347379
}
380+
381+
/**
382+
* @brief invoke a callable with the error, if present
383+
*
384+
* constraints:
385+
* - Callable must be invocable with at least 1 of the possible error types
386+
* - Callable must return void, if invocable
387+
*
388+
* @tparam Self deduced self type
389+
* @tparam F the type of the callable
390+
* @param self the current Result instance
391+
* @param f the callable
392+
*/
393+
template<typename Self, typename F>
394+
requires traits::AnyInvocable<Self, F, Errs...>
395+
&& traits::AllVoid<traits::inspect_error_return_t<Self, F, Errs>...>
396+
constexpr void inspect_error(this Self&& self, F&& f) {
397+
// if there's no error contained, return
398+
if (!self.has_error()) {
399+
return;
400+
}
401+
// otherwise, invoke the callable
402+
std::visit([&f](auto&& arg) {
403+
// even though this condition is impossible, it's necessary. Otherwise the
404+
// compiler will compile a branch where f is invoked with an unsupported argument type
405+
if constexpr (std::same_as<std::monostate, std::remove_cvref_t<decltype(arg)>>
406+
|| !traits::Invocable<Self, F, std::remove_cvref_t<decltype(arg)>>) {
407+
throw std::logic_error("This exception is unreachable");
408+
} else {
409+
std::invoke(f, std::forward<decltype(arg)>(arg));
410+
}
411+
}, std::forward<Self>(self).error);
412+
}
348413
};
349414
} // namespace zest

include/common/result_impl.hpp

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,14 @@ inline constexpr bool all_same_v<T, Ts...> =
242242
template<typename... Ts>
243243
concept AllSame = all_same_v<Ts...>;
244244

245+
/**
246+
* @brief Check whether all types in a pack are void
247+
*
248+
* @tparam Ts types to check
249+
*/
250+
template<typename... Ts>
251+
concept AllVoid = true && ((std::same_as<void, Ts> || std::same_as<ignored_type, Ts>) && ...);
252+
245253
/**
246254
* @brief Find the first type in a typename pack that satisfies the given condition
247255
*
@@ -308,17 +316,25 @@ template<typename R, typename F, typename E>
308316
concept Invocable = std::invocable<F, decltype((std::get<E>(std::declval<R>().error)))>;
309317

310318
/**
311-
* @brief check whether a callable is invocable with any of the given argument types
312-
*
313-
* @note Result::or_else helper
319+
* @brief check whether a callable is invocable with all of the given argument types
314320
*
315321
* @tparam R the Result
316322
* @tparam F the callable
317-
* @tparam Es the possible argument types passed to the callable
323+
* @tparam Es the possible error types passed to the callable
318324
*/
319325
template<typename R, typename F, typename... Es>
320326
concept AllInvocable = (Invocable<R, F, Es> && ...);
321327

328+
/**
329+
* @brief check whether a callable is invocable with any of the given argument types
330+
*
331+
* @tparam R the Result
332+
* @tparam F the callable
333+
* @tparam Es the possible error types passed to the callable
334+
*/
335+
template<typename R, typename F, typename... Es>
336+
concept AnyInvocable = (Invocable<R, F, Es> || ...);
337+
322338
/**
323339
* @brief check whether a callable is invocable given the Result and argument type
324340
*
@@ -386,6 +402,18 @@ using and_then_return_t = std::invoke_result_t<F, decltype((std::declval<R>().va
386402
template<typename R, typename F, typename E>
387403
using or_else_return_t = invoke_result_t<F, decltype((std::get<E>(std::declval<R>().error)))>;
388404

405+
/**
406+
* @brief get the return type of the callable passed to Result::inspect_error
407+
*
408+
* @note if the callable is not invocable, the type is ignored_type
409+
*
410+
* @tparam R the Result
411+
* @tparam F the callable
412+
* @tparam E the callable argument
413+
*/
414+
template<typename R, typename F, typename E>
415+
using inspect_error_return_t = or_else_return_t<R, F, E>;
416+
389417
/**
390418
* @brief check whether the callable return types are all Result
391419
*

0 commit comments

Comments
 (0)