diff --git a/CMakeLists.txt b/CMakeLists.txt index 08ddc97..bac448a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,13 @@ target_compile_features(continuable-base cxx_trailing_return_types 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) target_compile_options(continuable-base INTERFACE diff --git a/cmake/compiler/msvc.cmake b/cmake/compiler/msvc.cmake index 74f5705..824d2a8 100644 --- a/cmake/compiler/msvc.cmake +++ b/cmake/compiler/msvc.cmake @@ -50,11 +50,7 @@ target_compile_options(continuable-features-warnings INTERFACE /W4) -if (CTI_CONTINUABLE_WITH_CPP_LATEST) - target_compile_options(continuable-features-flags - INTERFACE - /std:c++latest) -else() +if (NOT CTI_CONTINUABLE_WITH_CPP_LATEST) target_compile_options(continuable-features-flags INTERFACE /std:c++14) diff --git a/include/continuable/continuable-promise-base.hpp b/include/continuable/continuable-promise-base.hpp index 298fd5b..0d5431a 100644 --- a/include/continuable/continuable-promise-base.hpp +++ b/include/continuable/continuable-promise-base.hpp @@ -101,9 +101,7 @@ public: /// \throws This method never throws an exception. /// /// \since 2.0.0 - void operator()(exception_arg_t tag, - exception_t exception) && - noexcept { + void operator()(exception_arg_t tag, exception_t exception) && noexcept { std::move(data_)(tag, std::move(exception)); } diff --git a/include/continuable/continuable-result.hpp b/include/continuable/continuable-result.hpp index d5699eb..af58cad 100644 --- a/include/continuable/continuable-result.hpp +++ b/include/continuable/continuable-result.hpp @@ -197,6 +197,19 @@ private: detail::container::flat_variant variant_; }; +template +decltype(auto) get(result& result) { + return detail::result_trait::template get(result); +} +template +decltype(auto) get(result const& result) { + return detail::result_trait::template get(result); +} +template +decltype(auto) get(result&& result) { + return detail::result_trait::template get(std::move(result)); +} + inline result<> make_result() { result<> result; result.set_value(); @@ -209,4 +222,23 @@ auto make_result(T&&... values) { /// \} } // 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 +struct tuple_size> + : std::integral_constant {}; + +template +struct tuple_element> + : tuple_element> {}; +#if defined(__clang__) || defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} // namespace std + #endif // CONTINUABLE_RESULT_HPP_INCLUDED diff --git a/include/continuable/detail/core/base.hpp b/include/continuable/detail/core/base.hpp index 16cc0f2..d1d067e 100644 --- a/include/continuable/detail/core/base.hpp +++ b/include/continuable/detail/core/base.hpp @@ -146,6 +146,22 @@ public: #define CONTINUABLE_BLOCK_TRY_END } #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 +constexpr auto invoke_callback(T&& callable, exception_arg_t exception_arg, + Args&&... args) noexcept { + return util::partial_invoke(std::integral_constant{}, + std::forward(callable), exception_arg, + std::forward(args)...); +} +template +constexpr auto invoke_callback(T&& callable, Args&&... args) noexcept { + return util::partial_invoke(std::integral_constant{}, + std::forward(callable), + std::forward(args)...); +} + /// Invokes the given callable object with the given arguments while /// marking the operation as non exceptional. template @@ -173,8 +189,8 @@ invoker_of(traits::identity>) { [](auto&& callback, auto&& next_callback, auto&&... args) { CONTINUABLE_BLOCK_TRY_BEGIN auto continuation_ = - util::partial_invoke(std::forward(callback), - std::forward(args)...); + invoke_callback(std::forward(callback), + std::forward(args)...); invoke_continuation( std::move(continuation_), @@ -191,8 +207,8 @@ constexpr auto invoker_of(traits::identity) { [](auto&& callback, auto&& next_callback, auto&&... args) { CONTINUABLE_BLOCK_TRY_BEGIN auto result = - util::partial_invoke(std::forward(callback), - std::forward(args)...); + invoke_callback(std::forward(callback), + std::forward(args)...); invoke_no_except(std::forward(next_callback), std::move(result)); @@ -206,8 +222,8 @@ inline auto invoker_of(traits::identity) { return make_invoker( [](auto&& callback, auto&& next_callback, auto&&... args) { CONTINUABLE_BLOCK_TRY_BEGIN - util::partial_invoke(std::forward(callback), - std::forward(args)...); + invoke_callback(std::forward(callback), + std::forward(args)...); invoke_no_except( std::forward(next_callback)); CONTINUABLE_BLOCK_TRY_END @@ -222,8 +238,8 @@ inline auto invoker_of(traits::identity) { (void)next_callback; CONTINUABLE_BLOCK_TRY_BEGIN empty_result result = - util::partial_invoke(std::forward(callback), - std::forward(args)...); + invoke_callback(std::forward(callback), + std::forward(args)...); // Don't invoke anything here since returning an empty result // cancels the asynchronous chain effectively. @@ -240,8 +256,8 @@ inline auto invoker_of(traits::identity) { util::unused(callback, next_callback, args...); CONTINUABLE_BLOCK_TRY_BEGIN exceptional_result result = - util::partial_invoke(std::forward(callback), - std::forward(args)...); + invoke_callback(std::forward(callback), + std::forward(args)...); // Forward the exception to the next available handler invoke_no_except(std::forward(next_callback), @@ -259,19 +275,19 @@ auto invoker_of(traits::identity>) { [](auto&& callback, auto&& next_callback, auto&&... args) { CONTINUABLE_BLOCK_TRY_BEGIN result result = - util::partial_invoke(std::forward(callback), - std::forward(args)...); + invoke_callback(std::forward(callback), + std::forward(args)...); if (result.is_value()) { // Workaround for MSVC not capturing the reference // correctly inside the lambda. using Next = decltype(next_callback); - result_trait::visit( - std::move(result), // + traits::unpack( [&](auto&&... values) { invoke_no_except(std::forward(next_callback), std::forward(values)...); - }); + }, + std::move(result)); } else if (result.is_exception()) { // Forward the exception to the next available handler @@ -292,9 +308,8 @@ auto invoker_of(traits::identity>) { inline auto sequenced_unpack_invoker() { return [](auto&& callback, auto&& next_callback, auto&&... args) { CONTINUABLE_BLOCK_TRY_BEGIN - auto result = - util::partial_invoke(std::forward(callback), - std::forward(args)...); + auto result = invoke_callback(std::forward(callback), + std::forward(args)...); // Workaround for MSVC not capturing the reference // correctly inside the lambda. @@ -326,8 +341,8 @@ constexpr auto invoker_of(traits::identity>) { inline auto exception_invoker_of(traits::identity) noexcept { return [](auto&& callback, auto&& next_callback, auto&&... args) { CONTINUABLE_BLOCK_TRY_BEGIN - util::invoke(std::forward(callback), - std::forward(args)...); + invoke_callback(std::forward(callback), + std::forward(args)...); // The legacy behaviour is not to proceed the chain // on the first invoked failure handler @@ -336,81 +351,16 @@ inline auto exception_invoker_of(traits::identity) noexcept { }; } -/*template +inline auto exception_invoker_of(traits::identity id) noexcept { + return invoker_of(id); +} +inline auto +exception_invoker_of(traits::identity id) noexcept { + return invoker_of(id); +} +template auto exception_invoker_of(traits::identity> id) noexcept { return invoker_of(id); -}*/ - -/// - empty_result -> -inline auto exception_invoker_of(traits::identity) { - return make_invoker( - [](auto&& callback, auto&& next_callback, auto&&... args) { - (void)next_callback; - CONTINUABLE_BLOCK_TRY_BEGIN - empty_result result = - util::invoke(std::forward(callback), - std::forward(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 -> -inline auto exception_invoker_of(traits::identity) { - 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(callback), - std::forward(args)...); - - // Forward the exception to the next available handler - invoke_no_except(std::forward(next_callback), - exception_arg_t{}, - std::move(result).get_exception()); - CONTINUABLE_BLOCK_TRY_END - }, - traits::identity<>{}); -} - -/// - result -> next_callback(?...) -template -auto exception_invoker_of(traits::identity>) { - return make_invoker( - [](auto&& callback, auto&& next_callback, auto&&... args) { - CONTINUABLE_BLOCK_TRY_BEGIN - result result = - util::invoke(std::forward(callback), - std::forward(args)...); - if (result.is_value()) { - // Workaround for MSVC not capturing the reference - // correctly inside the lambda. - using Next = decltype(next_callback); - - result_trait::visit( - std::move(result), // - [&](auto&&... values) { - invoke_no_except(std::forward(next_callback), - std::forward(values)...); - }); - - } else if (result.is_exception()) { - // Forward the exception to the next available handler - invoke_no_except( - std::forward(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{}); } #undef CONTINUABLE_BLOCK_TRY_BEGIN @@ -482,8 +432,10 @@ struct result_handler_base(this)->callback_), std::move(args)...))>{}; + constexpr auto result = + traits::identify(this)->callback_), + std::move(args)...))>{}; // Pick the correct invoker that handles decorating of the result auto invoker = decoration::invoker_of(result); @@ -512,9 +464,10 @@ template struct error_handler_base { /// The operator which is called when an error occurred void operator()(exception_arg_t, exception_t exception) && { - constexpr auto result = traits::identify(this)->callback_), - exception_arg_t{}, std::move(exception)))>{}; + constexpr auto result = + traits::identify(this)->callback_), exception_arg_t{}, + std::move(exception)))>{}; auto invoker = decoration::exception_invoker_of(result); @@ -645,7 +598,7 @@ next_hint_of(std::integral_constant, hints::signature_hint_tag /*current*/) { // Partial Invoke the given callback using Result = decltype( - util::partial_invoke(std::declval(), std::declval()...)); + decoration::invoke_callback(std::declval(), std::declval()...)); // Return the hint of thr given invoker return decltype(decoration::invoker_of(traits::identify{}).hint()){}; diff --git a/include/continuable/detail/utility/result-trait.hpp b/include/continuable/detail/utility/result-trait.hpp index 2101315..66c0647 100644 --- a/include/continuable/detail/utility/result-trait.hpp +++ b/include/continuable/detail/utility/result-trait.hpp @@ -53,13 +53,6 @@ struct result_trait<> { static constexpr void unwrap(surrogate_t) { } - - template - static auto visit(Result&& result, Mapper&& mapper) { - assert(result.is_value()); - (void)result; - return util::invoke(std::forward(mapper)); - } }; template struct result_trait { @@ -75,10 +68,9 @@ struct result_trait { return std::forward(unwrap); } - template - static auto visit(Result&& result, Mapper&& mapper) { - return util::invoke(std::forward(mapper), - std::forward(result).get_value()); + template + static decltype(auto) get(Result&& result) { + return std::forward(result).get_value(); } }; template @@ -96,10 +88,9 @@ struct result_trait { return std::forward(unwrap); } - template - static auto visit(Result&& result, Mapper&& mapper) { - return traits::unpack(std::forward(mapper), - std::forward(result).get_value()); + template + static decltype(auto) get(Result&& result) { + return std::get(std::forward(result).get_value()); } }; } // namespace detail diff --git a/include/continuable/detail/utility/traits.hpp b/include/continuable/detail/utility/traits.hpp index 7c88688..e22eddb 100644 --- a/include/continuable/detail/utility/traits.hpp +++ b/include/continuable/detail/utility/traits.hpp @@ -98,18 +98,20 @@ template using void_t = typename detail::deduce_to_void::type; #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 template constexpr auto unpack_impl(U&& unpacker, F&& first_sequenceable, std::integer_sequence) -> decltype(std::forward(unpacker)( - std::get(std::forward(first_sequenceable))...)) { + get(std::forward(first_sequenceable))...)) { (void)first_sequenceable; return std::forward(unpacker)( - std::get(std::forward(first_sequenceable))...); + get(std::forward(first_sequenceable))...); } -} // namespace detail +} // namespace detail_unpack /// Calls the given callable object with the content of the given sequenceable /// @@ -119,12 +121,13 @@ template >::value>> constexpr auto unpack(Callable&& obj, TupleLike&& tuple_like) - -> decltype(detail::unpack_impl(std::forward(obj), - std::forward(tuple_like), - Sequence{})) { + -> decltype(detail_unpack::unpack_impl(std::forward(obj), + std::forward(tuple_like), + Sequence{})) { - return detail::unpack_impl(std::forward(obj), - std::forward(tuple_like), Sequence{}); + return detail_unpack::unpack_impl(std::forward(obj), + std::forward(tuple_like), + Sequence{}); } namespace detail { diff --git a/include/continuable/detail/utility/util.hpp b/include/continuable/detail/utility/util.hpp index d0d3777..71a6f60 100644 --- a/include/continuable/detail/utility/util.hpp +++ b/include/continuable/detail/utility/util.hpp @@ -126,15 +126,17 @@ struct invocation_env { /// /// \note This function will assert statically if there is no way to call the /// given object with less arguments. -template -/*keep this inline*/ inline auto partial_invoke(T&& callable, Args&&... args) { +template +/*keep this inline*/ inline auto +partial_invoke(std::integral_constant, T&& callable, + Args&&... args) { // Test whether we are able to call the function with the given arguments. traits::is_invokable_from_tuple> is_invokable; // The implementation is done in a shortcut way so there are less // type instantiations needed to call the callable with its full signature. - using env = detail::invocation_env<0U>; + using env = detail::invocation_env; return env::partial_invoke_impl_shortcut( is_invokable, std::forward(callable), std::forward(args)...); } diff --git a/test/unit-test/multi/test-continuable-base-errors.cpp b/test/unit-test/multi/test-continuable-base-errors.cpp index 90676cd..48444ee 100644 --- a/test/unit-test/multi/test-continuable-base-errors.cpp +++ b/test/unit-test/multi/test-continuable-base-errors.cpp @@ -27,6 +27,8 @@ #include +using namespace cti; + TYPED_TEST(single_dimension_tests, are_completing_errors) { ASSERT_ASYNC_EXCEPTION_COMPLETION( 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_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{}) + .fail([&]() -> result { + EXPECT_FALSE(handled); + handled = true; + return cancel(); + })); + + ASSERT_TRUE(handled); +} diff --git a/test/unit-test/multi/test-continuable-base-multipath.cpp b/test/unit-test/multi/test-continuable-base-multipath.cpp index c6b5669..df098d3 100644 --- a/test/unit-test/multi/test-continuable-base-multipath.cpp +++ b/test/unit-test/multi/test-continuable-base-multipath.cpp @@ -140,3 +140,36 @@ TYPED_TEST(single_dimension_tests, multipath_exception_is_autocanceled) { })); 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{}) + .fail([](exception_t) -> result { + // Throw an exception from inside the exception handler + throw test_exception(); + })); +} +#endif