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

Commit bafcdf3

Browse files
committed
fix Result::or_else applying incorrect types to callable
1 parent 3b89291 commit bafcdf3

1 file changed

Lines changed: 39 additions & 14 deletions

File tree

include/common/result.hpp

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ template<typename... Ts>
211211
struct all_same : std::true_type {};
212212

213213
// special type that is ignored by the all_same trait
214-
struct all_same_ignored_type {};
214+
struct ignored_type {};
215215

216216
/**
217217
* @brief Check whether all types in a pack are the same
@@ -221,8 +221,7 @@ struct all_same_ignored_type {};
221221
*/
222222
template<typename T, typename... Ts>
223223
struct all_same<T, Ts...> :
224-
std::bool_constant<
225-
((std::is_same_v<T, Ts> || std::is_same_v<all_same_ignored_type, Ts>) && ...)> {};
224+
std::bool_constant<((std::is_same_v<T, Ts> || std::is_same_v<ignored_type, Ts>) && ...)> {};
226225

227226
/**
228227
* @brief Check whether all types in a pack are the same
@@ -239,7 +238,7 @@ struct first_type_that_satisfies {};
239238
// Specialization: at least one type in the pack
240239
template<typename Condition, typename T, typename... Ts>
241240
struct first_type_that_satisfies<Condition, T, Ts...> :
242-
std::conditional_t<
241+
std::conditional<
243242
Condition::template value<T>, // Check condition for T
244243
std::type_identity<T>, // If true, use T
245244
first_type_that_satisfies<Condition, Ts...> // Otherwise, check rest
@@ -249,6 +248,18 @@ struct first_type_that_satisfies<Condition, T, Ts...> :
249248
template<typename Condition, typename... Ts>
250249
using first_type_that_satisfies_t = typename first_type_that_satisfies<Condition, Ts...>::type;
251250

251+
template<typename F, typename... Args>
252+
struct invoke_result {
253+
using type = ignored_type;
254+
};
255+
256+
template<typename F, typename... Args>
257+
requires std::invocable<F, Args...>
258+
struct invoke_result<F, Args...> : std::invoke_result<F, Args...> {};
259+
260+
template<typename F, typename... Args>
261+
using invoke_result_t = invoke_result<F, Args...>::type;
262+
252263
} // namespace traits
253264

254265
/**
@@ -268,7 +279,7 @@ class Result {
268279
// or_else return type helper
269280
template<typename Self, typename F, typename E>
270281
using or_else_return_t =
271-
std::invoke_result_t<F, decltype((std::get<E>(std::declval<Self>().error)))>;
282+
traits::invoke_result_t<F, decltype((std::get<E>(std::declval<Self>().error)))>;
272283

273284
// or_else invocable callable helper
274285
template<typename Self, typename F, typename E>
@@ -414,7 +425,7 @@ class Result {
414425
// if the callable is invocable
415426
or_else_return_t<Self, F, Errs>,
416427
// if the callable is not invocable
417-
traits::all_same_ignored_type>...>
428+
traits::ignored_type>...>
418429
// the callable must return a Result or void if it is invocable with a given error
419430
// type
420431
&& (std::conditional_t<
@@ -433,7 +444,7 @@ class Result {
433444
std::conditional_t<
434445
traits::is_result_v<or_else_return_t<Self, F, Errs>>,
435446
// if the return is a Result
436-
std::is_same<T, typename or_else_return_t<Self, F, Errs>::value_type>,
447+
std::is_same<T, or_else_return_t<Self, F, Errs>>,
437448
// otherwise, if the return is not a Result
438449
std::true_type>,
439450
// otherwise, if the callable is not invocable
@@ -448,23 +459,37 @@ class Result {
448459
traits::is_result_v<CallableReturnType>,
449460
CallableReturnType,
450461
std::remove_cvref_t<Self>>;
462+
451463
// if there isn't an error, return the value
452464
if (!self.has_error()) {
453-
return std::forward<Self>(self).value;
465+
return ReturnType(std::forward<Self>(self).value);
466+
}
467+
468+
if constexpr (std::same_as<void, CallableReturnType>) {
469+
return std::visit([&f](auto&& arg) -> ReturnType {
470+
// even though this condition is impossible, it's necessary. Otherwise the
471+
// compiler will compile a branch where f is invoked with an unsupported argument
472+
// type
473+
if constexpr (!or_else_invocable_v<Self, F, decltype((arg))>
474+
|| std::
475+
is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
476+
throw std::logic_error("This exception is unreachable");
477+
} else {
478+
std::invoke(f, std::forward<decltype(arg)>(arg));
479+
return std::forward<decltype(arg)>(arg);
480+
}
481+
}, std::forward<Self>(self).error);
454482
}
455-
// if the callable returns void, then return this Result instance after invoking the
456-
// callable
457483
return std::visit([&f](auto&& arg) -> ReturnType {
458484
// even though this condition is impossible, it's necessary. Otherwise the
459485
// compiler will compile a branch where f is invoked with an unsupported argument
460486
// type
461-
if constexpr (!or_else_invocable_v<Self, F, decltype((arg))>) {
487+
if constexpr (!or_else_invocable_v<Self, F, decltype((arg))>
488+
|| std::is_same_v<std::monostate, std::remove_cvref_t<decltype(arg)>>) {
462489
throw std::logic_error("This exception is unreachable");
463-
} else if constexpr (std::same_as<ReturnType, void>) {
490+
} else {
464491
std::invoke(f, std::forward<decltype(arg)>(arg));
465492
return std::forward<decltype(arg)>(arg);
466-
} else {
467-
return std::invoke(f, std::forward<decltype(arg)>(arg));
468493
}
469494
}, std::forward<Self>(self).error);
470495
}

0 commit comments

Comments
 (0)