Make the failure handler partial applyable

* Make result C++17 destructible
* Add unit tests
This commit is contained in:
Denis Blank 2018-11-28 01:29:36 +01:00
parent bb7112eec2
commit 70c716bb28
10 changed files with 182 additions and 134 deletions

View File

@ -100,6 +100,13 @@ target_compile_features(continuable-base
cxx_trailing_return_types cxx_trailing_return_types
cxx_return_type_deduction) cxx_return_type_deduction)
if (CTI_CONTINUABLE_WITH_CPP_LATEST)
target_compile_features(continuable-base
INTERFACE
cxx_std_17)
endif()
if (CTI_CONTINUABLE_WITH_EXPERIMENTAL_COROUTINE) if (CTI_CONTINUABLE_WITH_EXPERIMENTAL_COROUTINE)
target_compile_options(continuable-base target_compile_options(continuable-base
INTERFACE INTERFACE

View File

@ -50,11 +50,7 @@ target_compile_options(continuable-features-warnings
INTERFACE INTERFACE
/W4) /W4)
if (CTI_CONTINUABLE_WITH_CPP_LATEST) if (NOT CTI_CONTINUABLE_WITH_CPP_LATEST)
target_compile_options(continuable-features-flags
INTERFACE
/std:c++latest)
else()
target_compile_options(continuable-features-flags target_compile_options(continuable-features-flags
INTERFACE INTERFACE
/std:c++14) /std:c++14)

View File

@ -101,9 +101,7 @@ public:
/// \throws This method never throws an exception. /// \throws This method never throws an exception.
/// ///
/// \since 2.0.0 /// \since 2.0.0
void operator()(exception_arg_t tag, void operator()(exception_arg_t tag, exception_t exception) && noexcept {
exception_t exception) &&
noexcept {
std::move(data_)(tag, std::move(exception)); std::move(data_)(tag, std::move(exception));
} }

View File

@ -197,6 +197,19 @@ private:
detail::container::flat_variant<surrogate_t, exception_t> variant_; detail::container::flat_variant<surrogate_t, exception_t> variant_;
}; };
template <std::size_t I, typename... T>
decltype(auto) get(result<T...>& result) {
return detail::result_trait<T...>::template get<I>(result);
}
template <std::size_t I, typename... T>
decltype(auto) get(result<T...> const& result) {
return detail::result_trait<T...>::template get<I>(result);
}
template <std::size_t I, typename... T>
decltype(auto) get(result<T...>&& result) {
return detail::result_trait<T...>::template get<I>(std::move(result));
}
inline result<> make_result() { inline result<> make_result() {
result<> result; result<> result;
result.set_value(); result.set_value();
@ -209,4 +222,23 @@ auto make_result(T&&... values) {
/// \} /// \}
} // namespace cti } // namespace cti
namespace std {
// The GCC standard library defines tuple_size as class and struct which
// triggers a warning here.
#if defined(__clang__) || defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmismatched-tags"
#endif
template <typename... Args>
struct tuple_size<cti::result<Args...>>
: std::integral_constant<size_t, sizeof...(Args)> {};
template <std::size_t I, typename... Args>
struct tuple_element<I, cti::result<Args...>>
: tuple_element<I, tuple<Args...>> {};
#if defined(__clang__) || defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
} // namespace std
#endif // CONTINUABLE_RESULT_HPP_INCLUDED #endif // CONTINUABLE_RESULT_HPP_INCLUDED

View File

@ -146,6 +146,22 @@ public:
#define CONTINUABLE_BLOCK_TRY_END } #define CONTINUABLE_BLOCK_TRY_END }
#endif // CONTINUABLE_HAS_EXCEPTIONS #endif // CONTINUABLE_HAS_EXCEPTIONS
/// Invokes the callback partially, keeps the exception_arg_t such that
/// we don't jump accidentally from the exception path to the result path.
template <typename T, typename... Args>
constexpr auto invoke_callback(T&& callable, exception_arg_t exception_arg,
Args&&... args) noexcept {
return util::partial_invoke(std::integral_constant<std::size_t, 01>{},
std::forward<T>(callable), exception_arg,
std::forward<Args>(args)...);
}
template <typename T, typename... Args>
constexpr auto invoke_callback(T&& callable, Args&&... args) noexcept {
return util::partial_invoke(std::integral_constant<std::size_t, 0U>{},
std::forward<T>(callable),
std::forward<Args>(args)...);
}
/// Invokes the given callable object with the given arguments while /// Invokes the given callable object with the given arguments while
/// marking the operation as non exceptional. /// marking the operation as non exceptional.
template <typename T, typename... Args> template <typename T, typename... Args>
@ -173,8 +189,8 @@ invoker_of(traits::identity<continuable_base<Data, Annotation>>) {
[](auto&& callback, auto&& next_callback, auto&&... args) { [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN CONTINUABLE_BLOCK_TRY_BEGIN
auto continuation_ = auto continuation_ =
util::partial_invoke(std::forward<decltype(callback)>(callback), invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...); std::forward<decltype(args)>(args)...);
invoke_continuation( invoke_continuation(
std::move(continuation_), std::move(continuation_),
@ -191,8 +207,8 @@ constexpr auto invoker_of(traits::identity<T>) {
[](auto&& callback, auto&& next_callback, auto&&... args) { [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN CONTINUABLE_BLOCK_TRY_BEGIN
auto result = auto result =
util::partial_invoke(std::forward<decltype(callback)>(callback), invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...); std::forward<decltype(args)>(args)...);
invoke_no_except(std::forward<decltype(next_callback)>(next_callback), invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
std::move(result)); std::move(result));
@ -206,8 +222,8 @@ inline auto invoker_of(traits::identity<void>) {
return make_invoker( return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) { [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN CONTINUABLE_BLOCK_TRY_BEGIN
util::partial_invoke(std::forward<decltype(callback)>(callback), invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...); std::forward<decltype(args)>(args)...);
invoke_no_except( invoke_no_except(
std::forward<decltype(next_callback)>(next_callback)); std::forward<decltype(next_callback)>(next_callback));
CONTINUABLE_BLOCK_TRY_END CONTINUABLE_BLOCK_TRY_END
@ -222,8 +238,8 @@ inline auto invoker_of(traits::identity<empty_result>) {
(void)next_callback; (void)next_callback;
CONTINUABLE_BLOCK_TRY_BEGIN CONTINUABLE_BLOCK_TRY_BEGIN
empty_result result = empty_result result =
util::partial_invoke(std::forward<decltype(callback)>(callback), invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...); std::forward<decltype(args)>(args)...);
// Don't invoke anything here since returning an empty result // Don't invoke anything here since returning an empty result
// cancels the asynchronous chain effectively. // cancels the asynchronous chain effectively.
@ -240,8 +256,8 @@ inline auto invoker_of(traits::identity<exceptional_result>) {
util::unused(callback, next_callback, args...); util::unused(callback, next_callback, args...);
CONTINUABLE_BLOCK_TRY_BEGIN CONTINUABLE_BLOCK_TRY_BEGIN
exceptional_result result = exceptional_result result =
util::partial_invoke(std::forward<decltype(callback)>(callback), invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...); std::forward<decltype(args)>(args)...);
// Forward the exception to the next available handler // Forward the exception to the next available handler
invoke_no_except(std::forward<decltype(next_callback)>(next_callback), invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
@ -259,19 +275,19 @@ auto invoker_of(traits::identity<result<Args...>>) {
[](auto&& callback, auto&& next_callback, auto&&... args) { [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN CONTINUABLE_BLOCK_TRY_BEGIN
result<Args...> result = result<Args...> result =
util::partial_invoke(std::forward<decltype(callback)>(callback), invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...); std::forward<decltype(args)>(args)...);
if (result.is_value()) { if (result.is_value()) {
// Workaround for MSVC not capturing the reference // Workaround for MSVC not capturing the reference
// correctly inside the lambda. // correctly inside the lambda.
using Next = decltype(next_callback); using Next = decltype(next_callback);
result_trait<Args...>::visit( traits::unpack(
std::move(result), //
[&](auto&&... values) { [&](auto&&... values) {
invoke_no_except(std::forward<Next>(next_callback), invoke_no_except(std::forward<Next>(next_callback),
std::forward<decltype(values)>(values)...); std::forward<decltype(values)>(values)...);
}); },
std::move(result));
} else if (result.is_exception()) { } else if (result.is_exception()) {
// Forward the exception to the next available handler // Forward the exception to the next available handler
@ -292,9 +308,8 @@ auto invoker_of(traits::identity<result<Args...>>) {
inline auto sequenced_unpack_invoker() { inline auto sequenced_unpack_invoker() {
return [](auto&& callback, auto&& next_callback, auto&&... args) { return [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN CONTINUABLE_BLOCK_TRY_BEGIN
auto result = auto result = invoke_callback(std::forward<decltype(callback)>(callback),
util::partial_invoke(std::forward<decltype(callback)>(callback), std::forward<decltype(args)>(args)...);
std::forward<decltype(args)>(args)...);
// Workaround for MSVC not capturing the reference // Workaround for MSVC not capturing the reference
// correctly inside the lambda. // correctly inside the lambda.
@ -326,8 +341,8 @@ constexpr auto invoker_of(traits::identity<std::tuple<Args...>>) {
inline auto exception_invoker_of(traits::identity<void>) noexcept { inline auto exception_invoker_of(traits::identity<void>) noexcept {
return [](auto&& callback, auto&& next_callback, auto&&... args) { return [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN CONTINUABLE_BLOCK_TRY_BEGIN
util::invoke(std::forward<decltype(callback)>(callback), invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...); std::forward<decltype(args)>(args)...);
// The legacy behaviour is not to proceed the chain // The legacy behaviour is not to proceed the chain
// on the first invoked failure handler // on the first invoked failure handler
@ -336,81 +351,16 @@ inline auto exception_invoker_of(traits::identity<void>) noexcept {
}; };
} }
/*template <typename... Args> inline auto exception_invoker_of(traits::identity<empty_result> id) noexcept {
return invoker_of(id);
}
inline auto
exception_invoker_of(traits::identity<exceptional_result> id) noexcept {
return invoker_of(id);
}
template <typename... Args>
auto exception_invoker_of(traits::identity<result<Args...>> id) noexcept { auto exception_invoker_of(traits::identity<result<Args...>> id) noexcept {
return invoker_of(id); return invoker_of(id);
}*/
/// - empty_result -> <cancel>
inline auto exception_invoker_of(traits::identity<empty_result>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
(void)next_callback;
CONTINUABLE_BLOCK_TRY_BEGIN
empty_result result =
util::invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Don't invoke anything here since returning an empty result
// cancels the asynchronous chain effectively.
(void)result;
CONTINUABLE_BLOCK_TRY_END
},
traits::identity<>{});
}
/// - exceptional_result -> <throw>
inline auto exception_invoker_of(traits::identity<exceptional_result>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
util::unused(callback, next_callback, args...);
CONTINUABLE_BLOCK_TRY_BEGIN
exceptional_result result =
util::invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Forward the exception to the next available handler
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
exception_arg_t{},
std::move(result).get_exception());
CONTINUABLE_BLOCK_TRY_END
},
traits::identity<>{});
}
/// - result<?...> -> next_callback(?...)
template <typename... Args>
auto exception_invoker_of(traits::identity<result<Args...>>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
result<Args...> result =
util::invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
if (result.is_value()) {
// Workaround for MSVC not capturing the reference
// correctly inside the lambda.
using Next = decltype(next_callback);
result_trait<Args...>::visit(
std::move(result), //
[&](auto&&... values) {
invoke_no_except(std::forward<Next>(next_callback),
std::forward<decltype(values)>(values)...);
});
} else if (result.is_exception()) {
// Forward the exception to the next available handler
invoke_no_except(
std::forward<decltype(next_callback)>(next_callback),
exception_arg_t{}, std::move(result).get_exception());
}
// Otherwise the result is empty and we are cancelling our
// asynchronous chain.
CONTINUABLE_BLOCK_TRY_END
},
traits::identity<Args...>{});
} }
#undef CONTINUABLE_BLOCK_TRY_BEGIN #undef CONTINUABLE_BLOCK_TRY_BEGIN
@ -482,8 +432,10 @@ struct result_handler_base<handle_results::yes, Base,
void operator()(Args... args) && { void operator()(Args... args) && {
// In order to retrieve the correct decorator we must know what the // In order to retrieve the correct decorator we must know what the
// result type is. // result type is.
constexpr auto result = traits::identify<decltype(util::partial_invoke( constexpr auto result =
std::move(static_cast<Base*>(this)->callback_), std::move(args)...))>{}; traits::identify<decltype(decoration::invoke_callback(
std::move(static_cast<Base*>(this)->callback_),
std::move(args)...))>{};
// Pick the correct invoker that handles decorating of the result // Pick the correct invoker that handles decorating of the result
auto invoker = decoration::invoker_of(result); auto invoker = decoration::invoker_of(result);
@ -512,9 +464,10 @@ template <typename Base>
struct error_handler_base<handle_errors::forward, Base> { struct error_handler_base<handle_errors::forward, Base> {
/// The operator which is called when an error occurred /// The operator which is called when an error occurred
void operator()(exception_arg_t, exception_t exception) && { void operator()(exception_arg_t, exception_t exception) && {
constexpr auto result = traits::identify<decltype( constexpr auto result =
util::partial_invoke(std::move(static_cast<Base*>(this)->callback_), traits::identify<decltype(decoration::invoke_callback(
exception_arg_t{}, std::move(exception)))>{}; std::move(static_cast<Base*>(this)->callback_), exception_arg_t{},
std::move(exception)))>{};
auto invoker = decoration::exception_invoker_of(result); auto invoker = decoration::exception_invoker_of(result);
@ -645,7 +598,7 @@ next_hint_of(std::integral_constant<handle_results, handle_results::yes>,
hints::signature_hint_tag<Args...> /*current*/) { hints::signature_hint_tag<Args...> /*current*/) {
// Partial Invoke the given callback // Partial Invoke the given callback
using Result = decltype( using Result = decltype(
util::partial_invoke(std::declval<T>(), std::declval<Args>()...)); decoration::invoke_callback(std::declval<T>(), std::declval<Args>()...));
// Return the hint of thr given invoker // Return the hint of thr given invoker
return decltype(decoration::invoker_of(traits::identify<Result>{}).hint()){}; return decltype(decoration::invoker_of(traits::identify<Result>{}).hint()){};

View File

@ -53,13 +53,6 @@ struct result_trait<> {
static constexpr void unwrap(surrogate_t) { static constexpr void unwrap(surrogate_t) {
} }
template <typename Result, typename Mapper>
static auto visit(Result&& result, Mapper&& mapper) {
assert(result.is_value());
(void)result;
return util::invoke(std::forward<Mapper>(mapper));
}
}; };
template <typename T> template <typename T>
struct result_trait<T> { struct result_trait<T> {
@ -75,10 +68,9 @@ struct result_trait<T> {
return std::forward<R>(unwrap); return std::forward<R>(unwrap);
} }
template <typename Result, typename Mapper> template <std::size_t I, typename Result>
static auto visit(Result&& result, Mapper&& mapper) { static decltype(auto) get(Result&& result) {
return util::invoke(std::forward<Mapper>(mapper), return std::forward<Result>(result).get_value();
std::forward<Result>(result).get_value());
} }
}; };
template <typename First, typename Second, typename... Rest> template <typename First, typename Second, typename... Rest>
@ -96,10 +88,9 @@ struct result_trait<First, Second, Rest...> {
return std::forward<R>(unwrap); return std::forward<R>(unwrap);
} }
template <typename Result, typename Mapper> template <std::size_t I, typename Result>
static auto visit(Result&& result, Mapper&& mapper) { static decltype(auto) get(Result&& result) {
return traits::unpack(std::forward<Mapper>(mapper), return std::get<I>(std::forward<Result>(result).get_value());
std::forward<Result>(result).get_value());
} }
}; };
} // namespace detail } // namespace detail

View File

@ -98,18 +98,20 @@ template <typename... T>
using void_t = typename detail::deduce_to_void<T...>::type; using void_t = typename detail::deduce_to_void<T...>::type;
#endif // CONTINUABLE_HAS_CXX17_VOID_T #endif // CONTINUABLE_HAS_CXX17_VOID_T
namespace detail { namespace detail_unpack {
using std::get;
/// Calls the given unpacker with the content of the given sequenceable /// Calls the given unpacker with the content of the given sequenceable
template <typename U, typename F, std::size_t... I> template <typename U, typename F, std::size_t... I>
constexpr auto unpack_impl(U&& unpacker, F&& first_sequenceable, constexpr auto unpack_impl(U&& unpacker, F&& first_sequenceable,
std::integer_sequence<std::size_t, I...>) std::integer_sequence<std::size_t, I...>)
-> decltype(std::forward<U>(unpacker)( -> decltype(std::forward<U>(unpacker)(
std::get<I>(std::forward<F>(first_sequenceable))...)) { get<I>(std::forward<F>(first_sequenceable))...)) {
(void)first_sequenceable; (void)first_sequenceable;
return std::forward<U>(unpacker)( return std::forward<U>(unpacker)(
std::get<I>(std::forward<F>(first_sequenceable))...); get<I>(std::forward<F>(first_sequenceable))...);
} }
} // namespace detail } // namespace detail_unpack
/// Calls the given callable object with the content of the given sequenceable /// Calls the given callable object with the content of the given sequenceable
/// ///
@ -119,12 +121,13 @@ template <typename Callable, typename TupleLike,
typename Sequence = std::make_index_sequence< typename Sequence = std::make_index_sequence<
std::tuple_size<std::decay_t<TupleLike>>::value>> std::tuple_size<std::decay_t<TupleLike>>::value>>
constexpr auto unpack(Callable&& obj, TupleLike&& tuple_like) constexpr auto unpack(Callable&& obj, TupleLike&& tuple_like)
-> decltype(detail::unpack_impl(std::forward<Callable>(obj), -> decltype(detail_unpack::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like), std::forward<TupleLike>(tuple_like),
Sequence{})) { Sequence{})) {
return detail::unpack_impl(std::forward<Callable>(obj), return detail_unpack::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like), Sequence{}); std::forward<TupleLike>(tuple_like),
Sequence{});
} }
namespace detail { namespace detail {

View File

@ -126,15 +126,17 @@ struct invocation_env {
/// ///
/// \note This function will assert statically if there is no way to call the /// \note This function will assert statically if there is no way to call the
/// given object with less arguments. /// given object with less arguments.
template <typename T, typename... Args> template <std::size_t KeepArgs, typename T, typename... Args>
/*keep this inline*/ inline auto partial_invoke(T&& callable, Args&&... args) { /*keep this inline*/ inline auto
partial_invoke(std::integral_constant<std::size_t, KeepArgs>, T&& callable,
Args&&... args) {
// Test whether we are able to call the function with the given arguments. // Test whether we are able to call the function with the given arguments.
traits::is_invokable_from_tuple<decltype(callable), std::tuple<Args...>> traits::is_invokable_from_tuple<decltype(callable), std::tuple<Args...>>
is_invokable; is_invokable;
// The implementation is done in a shortcut way so there are less // The implementation is done in a shortcut way so there are less
// type instantiations needed to call the callable with its full signature. // type instantiations needed to call the callable with its full signature.
using env = detail::invocation_env<0U>; using env = detail::invocation_env<KeepArgs>;
return env::partial_invoke_impl_shortcut( return env::partial_invoke_impl_shortcut(
is_invokable, std::forward<T>(callable), std::forward<Args>(args)...); is_invokable, std::forward<T>(callable), std::forward<Args>(args)...);
} }

View File

@ -27,6 +27,8 @@
#include <test-continuable.hpp> #include <test-continuable.hpp>
using namespace cti;
TYPED_TEST(single_dimension_tests, are_completing_errors) { TYPED_TEST(single_dimension_tests, are_completing_errors) {
ASSERT_ASYNC_EXCEPTION_COMPLETION( ASSERT_ASYNC_EXCEPTION_COMPLETION(
this->supply_exception(supply_test_exception())); this->supply_exception(supply_test_exception()));
@ -110,3 +112,34 @@ TYPED_TEST(single_dimension_tests, are_flow_error_accepting) {
ASSERT_ASYNC_INCOMPLETION(std::move(continuation)); ASSERT_ASYNC_INCOMPLETION(std::move(continuation));
ASSERT_TRUE(*handled); ASSERT_TRUE(*handled);
} }
TYPED_TEST(single_dimension_tests, are_exceptions_partial_applyable) {
bool handled = false;
ASSERT_ASYNC_INCOMPLETION(
this->supply_exception(supply_test_exception()).fail([&]() -> void {
EXPECT_FALSE(handled);
handled = true;
}));
ASSERT_TRUE(handled);
handled = false;
ASSERT_ASYNC_INCOMPLETION(this->supply_exception(supply_test_exception())
.fail([&]() -> empty_result {
EXPECT_FALSE(handled);
handled = true;
return cancel();
}));
ASSERT_TRUE(handled);
handled = false;
ASSERT_ASYNC_INCOMPLETION(
this->supply_exception(supply_test_exception(),
detail::traits::identity<int, int>{})
.fail([&]() -> result<int, int> {
EXPECT_FALSE(handled);
handled = true;
return cancel();
}));
ASSERT_TRUE(handled);
}

View File

@ -140,3 +140,36 @@ TYPED_TEST(single_dimension_tests, multipath_exception_is_autocanceled) {
})); }));
ASSERT_TRUE(caught); ASSERT_TRUE(caught);
} }
#if !defined(CONTINUABLE_WITH_NO_EXCEPTIONS)
// Enable this test only if we support exceptions
TYPED_TEST(single_dimension_tests, multipath_exception_can_rethrow) {
ASSERT_ASYNC_EXCEPTION_COMPLETION(
this->supply_exception(supply_test_exception()).fail([](exception_t) {
// Throw an exception from inside the exception handler
throw test_exception();
}));
ASSERT_ASYNC_EXCEPTION_COMPLETION(
this->supply_exception(supply_test_exception())
.fail([](exception_t) -> empty_result {
// Throw an exception from inside the exception handler
throw test_exception();
}));
ASSERT_ASYNC_EXCEPTION_COMPLETION(
this->supply_exception(supply_test_exception())
.fail([](exception_t) -> exceptional_result {
// Throw an exception from inside the exception handler
throw test_exception();
}));
ASSERT_ASYNC_EXCEPTION_COMPLETION(
this->supply_exception(supply_test_exception(), identity<int>{})
.fail([](exception_t) -> result<int> {
// Throw an exception from inside the exception handler
throw test_exception();
}));
}
#endif