From cd2504da2d0b146fa31608b3a7af9a2c5c2af6a6 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Mon, 26 Feb 2024 22:27:55 -0800 Subject: [PATCH] Working code with `on`, `then`, `start` and `sync_wait` algorithms. --- test/CMakeLists.txt | 1 + test/initial_draft.cpp | 172 ++++++++++++++++++++++++----------------- 2 files changed, 100 insertions(+), 73 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9394e30..1a8c9c2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,6 +30,7 @@ add_test(NAME cli.version_matches COMMAND intro --version) set_tests_properties(cli.version_matches PROPERTIES PASS_REGULAR_EXPRESSION "${PROJECT_VERSION}") add_executable(tests tuple_tests.cpp initial_draft.cpp) +target_compile_options(tests PRIVATE -ftemplate-backtrace-limit=0) target_link_libraries( tests PRIVATE chains::chains_warnings diff --git a/test/initial_draft.cpp b/test/initial_draft.cpp index a68fcb1..55a82c0 100644 --- a/test/initial_draft.cpp +++ b/test/initial_draft.cpp @@ -24,23 +24,6 @@ set on the receiver. namespace chains::inline v1 { -/* - An scheduler is a function that takes a function and a set of arguments and applies the - arguments to the function. The application may be scheduled in another context and the scheduler - may inject additional arguments into the function. -*/ - -template -struct scheduler { - template - using result_type = std::invoke_result_t; - F _f; - template - auto operator()(Args&&... args) const { - return std::invoke(_f, std::forward(args)...); - } -}; - /* segment is invoked with a receiver - @@ -48,7 +31,21 @@ segment is invoked with a receiver - */ -template , class Applicator, class... Fs> +template +struct type {}; + +template +class segment; + +#if 0 +template +inline auto make_segment(Applicator&& apply, Fs&&... fs) { + return segment, std::decay_t...>{ + std::forward(apply), std::forward(fs)...}; +} +#endif + +template class segment { std::tuple _functions; Applicator _apply; @@ -67,9 +64,9 @@ class segment { return tuple_compose(std::move(_functions))(std::forward(args)...); } - explicit segment(Applicator&& apply, std::tuple&& functions) + explicit segment(type, Applicator&& apply, std::tuple&& functions) : _functions{std::move(functions)}, _apply{std::move(apply)} {} - explicit segment(Applicator&& apply, Fs&&... functions) + explicit segment(type, Applicator&& apply, Fs&&... functions) : _functions{std::move(functions)...}, _apply{std::move(apply)} {} /* @@ -83,8 +80,9 @@ class segment { template auto append(F&& f) && { - return chains::segment{std::move(_apply), std::tuple_cat(std::move(_functions), - std::tuple{std::forward(f)})}; + return chains::segment{ + type{}, std::move(_apply), + std::tuple_cat(std::move(_functions), std::tuple{std::forward(f)})}; } #if 0 @@ -131,25 +129,20 @@ constexpr auto fold_over(Fold fold, Segments&& segments) { } // namespace detail -#error "We need to account for the segment providing the argument to the tuple in the compose." - -template -using segment_result_type = - decltype(std::declval().result_type_helper(std::declval()...)); - /* simplify this code by handing the multi-argument case earlier (somehow). */ -template +template class chain { Tail _tail; - segment _head; + segment _head; /// Return a lambda with the signature of /// head( tail( tail<1>( tail<0>( auto&& args... ) ) ) ) /// for computing the result type of this chain. - static consteval auto result_type_helper(Tail&& tail, segment&& head) { + static consteval auto result_type_helper(Tail&& tail, + segment&& head) { return detail::fold_over( [](auto fold, auto&& first, auto&&... rest) { if constexpr (sizeof...(rest) == 0) { @@ -163,7 +156,7 @@ class chain { }; } }, - std::tuple_cat(std::move(tail), std::tuple{std::move(head)})); + std::tuple_cat(STLAB_FWD(tail), std::tuple{STLAB_FWD(head)})); } template @@ -185,12 +178,27 @@ class chain { std::tuple_cat(std::move(_tail), std::tuple{std::move(_head)})); } + template + struct result_type_void_injects { + using type = decltype(result_type_helper( + std::declval(), + std::declval>())(std::declval()...)); + }; + + template + struct result_type_injects { + using type = decltype(result_type_helper( + std::declval(), std::declval>())( + std::declval(), std::declval()...)); + }; + public: template - using result_type = decltype(result_type_helper( - std::declval(), std::declval>())(std::declval()...)); + using result_type = std::conditional_t, + result_type_void_injects, + result_type_injects>::type; - explicit chain(Tail&& tail, segment&& head) + explicit chain(Tail&& tail, segment&& head) : _tail{std::move(tail)}, _head{std::move(head)} {} /* @@ -209,8 +217,8 @@ class chain { return chains::chain{std::move(_tail), std::move(_head).append(std::forward(f))}; } - template - auto append(segment&& head) && { + template + auto append(segment&& head) && { return chains::chain{std::tuple_cat(std::move(_tail), std::make_tuple(std::move(_head))), std::move(head)}; } @@ -221,6 +229,7 @@ class chain { std::forward(args)...); } +#if 0 template [[deprecated]] auto operator()(Args&&... args) && { using result_t = result_type; @@ -229,23 +238,25 @@ class chain { invoke(std::move(receiver), std::forward(args)...); return std::move(future); } +#endif template friend auto operator|(chain&& c, F&& f) { return std::move(c).append(std::forward(f)); } - template - friend auto operator|(chain&& c, segment&& head) { + template + friend auto operator|(chain&& c, segment&& head) { return std::move(c).append(std::move(head)); } }; -template -chain(Tail&& tail, segment&& head) -> chain; +template +chain(Tail&& tail, segment&& head) + -> chain; -template -inline auto operator|(segment&& head, F&& f) { +template +inline auto operator|(segment&& head, F&& f) { return chain{std::tuple<>{}, std::move(head).append(std::forward(f))}; } @@ -266,22 +277,23 @@ last item in the chain as a segment. */ template inline auto on(E&& executor) { - return segment{[_executor = std::forward(executor)](auto&& f, auto&&... args) mutable { - std::move(_executor)( - [_f = std::forward(f), - _args = std::tuple{std::forward(args)...}]() mutable noexcept { - std::apply(std::move(_f), std::move(_args)); - }); - return std::monostate{}; - }}; + return segment{ + type{}, [_executor = std::forward(executor)](auto&& f, auto&&... args) mutable { + std::move(_executor)( + [_f = std::forward(f), + _args = std::tuple{std::forward(args)...}]() mutable noexcept { + std::apply(std::move(_f), std::move(_args)); + }); + // return std::monostate{}; + }}; } /* The `then` algorithm takes a future and returns a segment (chain) that will schedule the segment as a continuation of the future. - The segment returns void so the future is (kind of) detached - but this should be done without - the overhead of a future::detach. + The segment returns void so the future is (kind of) detached - but this should be done + without the overhead of a future::detach. How is cancellation handled here? Let's say we have this: @@ -293,9 +305,11 @@ inline auto on(E&& executor) { template inline auto then(F&& future) { - return chain{std::tuple<>{}, segment{[_future = std::forward(future)](auto&& f) mutable { - return std::move(_future).then(std::forward(f)); - }}}; + return chain{std::tuple<>{}, + segment{type::result_type>{}, + [_future = std::forward(future)](auto&& f) mutable { + return std::move(_future).then(std::forward(f)); + }}}; } // TODO: (sean-parent) - should we make this pipeable? @@ -305,14 +319,23 @@ template inline auto start(Chain&& chain, Args&&... args) { using result_t = Chain::template result_type; using invoke_t = decltype(std::forward(chain).invoke( - std::move(std::declval>()), std::forward(args)...)); - auto p = std::make_shared>(); - auto [receiver, future] = - stlab::package(stlab::immediate_executor, [p](auto&& value) { - return std::forward(value); - }); - *p = std::forward(chain).invoke(std::move(receiver), std::forward(args)...); - return std::move(future); + std::declval>(), std::forward(args)...)); + if constexpr (std::is_same_v) { + auto [receiver, future] = + stlab::package(stlab::immediate_executor, [](auto&& value) { + return std::forward(value); + }); + std::forward(chain).invoke(std::move(receiver), std::forward(args)...); + return std::move(future); + } else { + auto p = std::make_shared>(); + auto [receiver, future] = + stlab::package(stlab::immediate_executor, [p](auto&& value) { + return std::forward(value); + }); + *p = std::forward(chain).invoke(std::move(receiver), std::forward(args)...); + return std::move(future); + } } template @@ -355,8 +378,8 @@ inline auto sync_wait(Chain&& chain, Args&&... args) { } receiver; /* - REVISIT: (sean-parent) - chain invoke doesn't work with std::ref(receiver). We should fix - that but for now create a receiver-ref. + REVISIT: (sean-parent) - chain invoke doesn't work with std::ref(receiver). We should + fix that but for now create a receiver-ref. */ auto hold = std::forward(chain).invoke(receiver_ref{&receiver}, @@ -372,12 +395,13 @@ inline auto sync_wait(Chain&& chain, Args&&... args) { } /* - TODO: The ergonimics of chains are painful with three arguements. We could reduce to a single - argument or move to a concept? Here I really want the forward reference to be an rvalue ref. + TODO: The ergonimics of chains are painful with three arguements. We could reduce to a + single argument or move to a concept? Here I really want the forward reference to be an + rvalue ref. - The implementation of sync_wait is complicated by the fact that the promise is currently hard/ - wired into the chain. sync_wait needs to be able to invoke the promise/receiver - _then_ flag - the condition that it is ready. + The implementation of sync_wait is complicated by the fact that the promise is currently + hard/ wired into the chain. sync_wait needs to be able to invoke the promise/receiver - + _then_ flag the condition that it is ready. */ } // namespace chains::inline v1 @@ -398,6 +422,8 @@ TEST_CASE("Initial draft", "[initial_draft]") { cout << "Hello from thread: " << std::this_thread::get_id() << "\n"; return 42; }; + // std::cout << typeid(decltype(a0)::result_type<>).name() << "\n"; + // auto future = start(std::move(a0)); auto a1 = std::move(a0) | on(default_executor) | [](int x) { cout << "received: " << x << " on thread: " << std::this_thread::get_id() << "\n"; @@ -409,7 +435,7 @@ TEST_CASE("Initial draft", "[initial_draft]") { cout << "Ready to go async!\n"; #if 0 - auto a2 = then(std::move(a1)()) | [](std::string s){ + auto a2 = then(std::move(a1)) | [](std::string s) { cout << s << "<-- \n"; return 0; }; @@ -437,7 +463,7 @@ TEST_CASE("Initial draft", "[initial_draft]") { // std::cout << await(start(std::move(a1))) << "\n"; auto future = start(std::move(a1)); - auto a2 = then(future) | [](std::string s) { std::cout << s << "<-- \n"; }; + auto a2 = then(future) | [](std::string s) { return s + "<-- \n"; }; std::cout << sync_wait(std::move(a2)) << "\n";