diff --git a/include/continuable/continuable-base.hpp b/include/continuable/continuable-base.hpp index 6436c2a..1b19801 100644 --- a/include/continuable/continuable-base.hpp +++ b/include/continuable/continuable-base.hpp @@ -427,6 +427,114 @@ inline auto or_folding() { template using fail = std::integral_constant::value>; +namespace detail { +template > +struct is_invokable_impl : std::common_type {}; + +template +struct is_invokable_impl< + T, std::tuple, + void_t()(std::declval()...))>> + : std::common_type {}; +} // end namespace detail + +/// Deduces to a std::true_type if the given type is callable with the arguments +/// inside the given tuple. +/// The main reason for implementing it with the detection idiom instead of +/// hana like detection is that MSVC has issues with capturing raw template +/// arguments inside lambda closures. +/// +/// ```cpp +/// util::is_invokable_t> +/// ``` +template +using is_invokable_t = typename detail::is_invokable_impl::type; + +namespace detail { +/// Forwards every element in the tuple except the last one +template auto forward_except_last(T&& sequenceable) { + auto size = pack_size_of(identity_of(sequenceable)) - size_constant_of<1>(); + auto sequence = std::make_index_sequence(); + + return unpack(std::forward(sequenceable), + [](auto&&... args) { + return std::forward_as_tuple( + std::forward(args)...); + }, + sequence); +} + +/// We are able to call the callable with the arguments given in the tuple +template +auto partial_invoke_impl(std::true_type, T&& callable, + std::tuple args) { + return unpack(std::move(args), [&](auto&&... arg) { + return std::forward(callable)(std::forward(arg)...); + }); +} + +/// We were unable to call the callable with the arguments in the tuple. +/// Remove the last argument from the tuple and try it again. +template +auto partial_invoke_impl(std::false_type, T&& callable, + std::tuple args) { + + // If you are encountering this assertion you tried to attach a callback + // which can't accept the arguments of the continuation. + // + // ```cpp + // continuable c; + // std::move(c).then([](std::vector v) { /*...*/ }) + // ``` + static_assert( + sizeof...(Args) > 0, + "There is no way to call the given object with these arguments!"); + + // Remove the last argument from the tuple + auto next = forward_except_last(std::move(args)); + + // Test whether we are able to call the function with the given tuple + is_invokable_t is_invokable; + + return partial_invoke_impl(is_invokable, std::forward(callable), + std::move(next)); +} + +/// Shortcut - we can call the callable directly +template +auto partial_invoke_impl_shortcut(std::true_type, T&& callable, + Args&&... args) { + return std::forward(callable)(std::forward(args)...); +} + +/// Failed shortcut - we were unable to invoke the callable with the +/// original arguments. +template +auto partial_invoke_impl_shortcut(std::false_type failed, T&& callable, + Args&&... args) { + + // Our shortcut failed, convert the arguments into a forwarding tuple + return partial_invoke_impl( + failed, std::forward(callable), + std::forward_as_tuple(std::forward(args)...)); +} +} // end namespace detail + +/// Partially invokes the given callable with the given arguments. +/// +/// \note This function will assert statically if there is no way to call the +/// given object with less arguments. +template +auto partial_invoke(T&& callable, Args&&... args) { + // Test whether we are able to call the function with the given arguments. + is_invokable_t> 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. + return detail::partial_invoke_impl_shortcut( + is_invokable, std::forward(callable), std::forward(args)...); +} + // Class for making child classes non copyable struct non_copyable { non_copyable() = default; diff --git a/test/playground/CMakeLists.txt b/test/playground/CMakeLists.txt index e29c388..461b71c 100644 --- a/test/playground/CMakeLists.txt +++ b/test/playground/CMakeLists.txt @@ -4,3 +4,6 @@ add_executable(test-playground target_link_libraries(test-playground PRIVATE continuable) + +add_test(NAME continuable-playground-tests + COMMAND test-playground) diff --git a/test/playground/test-playground.cpp b/test/playground/test-playground.cpp index 076a254..0e355f2 100644 --- a/test/playground/test-playground.cpp +++ b/test/playground/test-playground.cpp @@ -22,16 +22,31 @@ #include "continuable/continuable.hpp" -auto invoke() { - return cti::make_continuable([](auto&& callback) { callback(); }); +using namespace cti::detail; +using namespace cti::detail::util; + +/// Predicate to check whether an object is callable with the given arguments +template auto is_invokable_with(identity) { + return [](auto&& callable) { + (void)callable; + return is_invokable_t>{}; + }; } -void trythestuff() { - auto future = invoke().futurize(); - future.get(); +template +auto reverse(identity<>, identity right = identity<>{}) { + return right; +} +template +auto reverse(identity, identity = identity<>{}) { + return reverse(identity{}, identity{}); } int main(int, char**) { - trythestuff(); + + auto cb = [](int, int) { return 0; }; + + partial_invoke(cb, 0, 0, 0, 0, 0, 0, 0); + return 0; }