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_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

View File

@ -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)

View File

@ -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));
}

View File

@ -197,6 +197,19 @@ private:
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() {
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 <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

View File

@ -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 <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
/// marking the operation as non exceptional.
template <typename T, typename... Args>
@ -173,8 +189,8 @@ invoker_of(traits::identity<continuable_base<Data, Annotation>>) {
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto continuation_ =
util::partial_invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_continuation(
std::move(continuation_),
@ -191,8 +207,8 @@ constexpr auto invoker_of(traits::identity<T>) {
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto result =
util::partial_invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
std::move(result));
@ -206,8 +222,8 @@ inline auto invoker_of(traits::identity<void>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
util::partial_invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_no_except(
std::forward<decltype(next_callback)>(next_callback));
CONTINUABLE_BLOCK_TRY_END
@ -222,8 +238,8 @@ inline auto invoker_of(traits::identity<empty_result>) {
(void)next_callback;
CONTINUABLE_BLOCK_TRY_BEGIN
empty_result result =
util::partial_invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_callback(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.
@ -240,8 +256,8 @@ inline auto invoker_of(traits::identity<exceptional_result>) {
util::unused(callback, next_callback, args...);
CONTINUABLE_BLOCK_TRY_BEGIN
exceptional_result result =
util::partial_invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_callback(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),
@ -259,19 +275,19 @@ auto invoker_of(traits::identity<result<Args...>>) {
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
result<Args...> result =
util::partial_invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_callback(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), //
traits::unpack(
[&](auto&&... values) {
invoke_no_except(std::forward<Next>(next_callback),
std::forward<decltype(values)>(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<result<Args...>>) {
inline auto sequenced_unpack_invoker() {
return [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto result =
util::partial_invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
auto result = invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Workaround for MSVC not capturing the reference
// 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 {
return [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
util::invoke(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(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<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 {
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
@ -482,8 +432,10 @@ struct result_handler_base<handle_results::yes, Base,
void operator()(Args... args) && {
// In order to retrieve the correct decorator we must know what the
// result type is.
constexpr auto result = traits::identify<decltype(util::partial_invoke(
std::move(static_cast<Base*>(this)->callback_), std::move(args)...))>{};
constexpr auto result =
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
auto invoker = decoration::invoker_of(result);
@ -512,9 +464,10 @@ template <typename Base>
struct error_handler_base<handle_errors::forward, Base> {
/// The operator which is called when an error occurred
void operator()(exception_arg_t, exception_t exception) && {
constexpr auto result = traits::identify<decltype(
util::partial_invoke(std::move(static_cast<Base*>(this)->callback_),
exception_arg_t{}, std::move(exception)))>{};
constexpr auto result =
traits::identify<decltype(decoration::invoke_callback(
std::move(static_cast<Base*>(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<handle_results, handle_results::yes>,
hints::signature_hint_tag<Args...> /*current*/) {
// Partial Invoke the given callback
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 decltype(decoration::invoker_of(traits::identify<Result>{}).hint()){};

View File

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

View File

@ -98,18 +98,20 @@ template <typename... T>
using void_t = typename detail::deduce_to_void<T...>::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 <typename U, typename F, std::size_t... I>
constexpr auto unpack_impl(U&& unpacker, F&& first_sequenceable,
std::integer_sequence<std::size_t, I...>)
-> decltype(std::forward<U>(unpacker)(
std::get<I>(std::forward<F>(first_sequenceable))...)) {
get<I>(std::forward<F>(first_sequenceable))...)) {
(void)first_sequenceable;
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
///
@ -119,12 +121,13 @@ template <typename Callable, typename TupleLike,
typename Sequence = std::make_index_sequence<
std::tuple_size<std::decay_t<TupleLike>>::value>>
constexpr auto unpack(Callable&& obj, TupleLike&& tuple_like)
-> decltype(detail::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like),
Sequence{})) {
-> decltype(detail_unpack::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like),
Sequence{})) {
return detail::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like), Sequence{});
return detail_unpack::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like),
Sequence{});
}
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
/// given object with less arguments.
template <typename T, typename... Args>
/*keep this inline*/ inline auto partial_invoke(T&& callable, Args&&... args) {
template <std::size_t KeepArgs, typename T, typename... 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.
traits::is_invokable_from_tuple<decltype(callable), std::tuple<Args...>>
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<KeepArgs>;
return env::partial_invoke_impl_shortcut(
is_invokable, std::forward<T>(callable), std::forward<Args>(args)...);
}

View File

@ -27,6 +27,8 @@
#include <test-continuable.hpp>
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<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);
}
#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