From d93c694092f69627f7ed342b2818ebf51aed7cd5 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sat, 21 Aug 2021 19:15:55 -0700 Subject: [PATCH 1/7] All awaitables are implicitly typed senders --- examples/Makefile | 5 +- examples/hello_coro.cpp | 31 +++++++++ examples/task.hpp | 93 +++++++++++++++++++++++++ include/concepts.hpp | 16 +++++ include/coroutine.hpp | 58 ++++++++++++++++ include/execution.hpp | 150 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 examples/hello_coro.cpp create mode 100644 examples/task.hpp create mode 100644 include/coroutine.hpp diff --git a/examples/Makefile b/examples/Makefile index a07c81284..7f1de6fbc 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,7 +1,7 @@ BUILDDIR = build IDIR =../include CXX=clang++-12 -CXXFLAGS=-I$(IDIR) -std=c++20 +CXXFLAGS=-I$(IDIR) -std=c++20 -stdlib=libc++ LIBS=-pthread @@ -18,6 +18,9 @@ $(BUILDDIR)/%.o: %.cpp setup execution hello_world: $(BUILDDIR)/hello_world.o $(CXX) -o $(BUILDDIR)/$@ $^ $(CXXFLAGS) $(LIBS) +hello_coro: $(BUILDDIR)/hello_coro.o + $(CXX) -o $(BUILDDIR)/$@ $^ $(CXXFLAGS) $(LIBS) + .PHONY: clean clean: diff --git a/examples/hello_coro.cpp b/examples/hello_coro.cpp new file mode 100644 index 000000000..7e0a81499 --- /dev/null +++ b/examples/hello_coro.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) NVIDIA + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +// Pull in the reference implementation of P2300: +#include + +#include "./task.hpp" + +task async_answer() { + co_return 42; +} + +int main() { + // Awaitables are implicitly senders: + auto [i] = std::this_thread::sync_wait(async_answer()).value(); + std::cout << "The answer is " << i << '\n'; +} diff --git a/examples/task.hpp b/examples/task.hpp new file mode 100644 index 000000000..52583f819 --- /dev/null +++ b/examples/task.hpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) NVIDIA + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include + +#include +#include + +template +struct task { + struct promise_type; + struct final_awaitable { + bool await_ready() const noexcept { + return false; + } + auto await_suspend(coro::coroutine_handle h) const noexcept { + return h.promise().parent_; + } + void await_resume() const noexcept { + } + }; + struct promise_type { + task get_return_object() noexcept { + return task(coro::coroutine_handle::from_promise(*this)); + } + coro::suspend_always initial_suspend() noexcept { + return {}; + } + final_awaitable final_suspend() noexcept { + return {}; + } + void unhandled_exception() noexcept { + data_.template emplace<2>(std::current_exception()); + } + void return_value(T value) noexcept { + data_.template emplace<1>(std::move(value)); + } + std::variant data_{}; + coro::coroutine_handle<> parent_{}; + }; + + task(task&& that) noexcept + : coro_(std::exchange(that.coro_, {})) + {} + + ~task() { + if (coro_) + coro_.destroy(); + } + + struct task_awaitable { + task& t; + bool await_ready() const noexcept { + return false; + } + auto await_suspend(coro::coroutine_handle<> parent) noexcept { + t.coro_.promise().parent_ = parent; + return t.coro_; + } + T await_resume() const { + if (t.coro_.promise().data_.index() == 2) + std::rethrow_exception(std::get<2>(t.coro_.promise().data_)); + return std::get(t.coro_.promise().data_); + } + }; + + friend task_awaitable operator co_await(task&& t) noexcept { + return task_awaitable{t}; + } + +private: + explicit task(coro::coroutine_handle coro) noexcept + : coro_(coro) + {} + coro::coroutine_handle coro_; +}; diff --git a/include/concepts.hpp b/include/concepts.hpp index 5b9d3535e..309943b40 100644 --- a/include/concepts.hpp +++ b/include/concepts.hpp @@ -24,6 +24,22 @@ namespace std { // C++20 concepts + #if defined(__clang__) + template + concept same_as = __is_same(A, B) && __is_same(B, A); + #elif defined(__GNUC__) + template + concept same_as = __is_same_as(A, B) && __is_same_as(B, A); + #else + template + inline constexpr bool __same_as_v = false; + template + inline constexpr bool __same_as_v = true; + + template + concept same_as = __same_as_v && __same_as_v; + #endif + template concept derived_from = is_base_of_v && diff --git a/include/coroutine.hpp b/include/coroutine.hpp new file mode 100644 index 000000000..9253f1a24 --- /dev/null +++ b/include/coroutine.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) NVIDIA + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#if __has_include() +#include +namespace coro = std; +#else +#include +namespace coro = std::experimental; +#endif + +namespace std { + // Defined some concepts and utilities for working with awaitables + template + concept __awaiter = + requires (A&& a) { + {((A&&) a).await_ready()} -> same_as; + ((A&&) a).await_resume(); + }; + + template + decltype(auto) __get_awaiter(T&& t) { + if constexpr (requires { ((T&&) t).operator co_await(); }) { + return ((T&&) t).operator co_await(); + } + else if constexpr (requires { operator co_await((T&&) t); }) { + return operator co_await((T&&) t); + } + else { + return (T&&) t; + } + } + + template + concept __awaitable = + requires (A&& a) { + {std::__get_awaiter((A&&) a)} -> __awaiter; + }; + + template <__awaitable A> + using __await_result_t = decltype(std::__get_awaiter(std::declval()).await_resume()); +} diff --git a/include/execution.hpp b/include/execution.hpp index 732179d9f..eb5d5b9bc 100644 --- a/include/execution.hpp +++ b/include/execution.hpp @@ -26,6 +26,7 @@ #include <__utility.hpp> #include #include +#include namespace std::execution { template class, template class> class> @@ -57,6 +58,15 @@ namespace std::execution { static constexpr bool sends_done = true; }; + template + struct __sender_of { + template class Tuple, template class Variant> + using value_types = Variant>; + template class Variant> + using error_types = Variant; + static constexpr bool sends_done = SendsDone; + }; + template struct __typed_sender { template class Tuple, template class Variant> @@ -72,6 +82,12 @@ namespace std::execution { return __typed_sender{}; } else if constexpr (derived_from) { return sender_base{}; + } else if constexpr (__awaitable) { // NOT TO SPEC + if constexpr (is_void_v<__await_result_t>) { + return __void_sender{}; + } else { + return __sender_of>{}; + } } else { return __no_sender_traits{}; } @@ -107,7 +123,7 @@ namespace std::execution { inline constexpr struct set_done_t { template requires tag_invocable - void operator()(R&& r) const + void operator()(R&& r) const noexcept(nothrow_tag_invocable) { (void) tag_invoke(set_done_t{}, (R&&) r); } @@ -173,6 +189,123 @@ namespace std::execution { {start(o)} noexcept; }; + ///////////////////////////////////////////////////////////////////////////// + // NOT TO SPEC: __connect_awaitable_ + inline namespace __connect_awaitable_ { + namespace __impl { + template + class __op { + using R = __t; + public: + struct promise_type { + template + explicit promise_type(A&, R& r) noexcept + : r_(r) + {} + + __op get_return_object() noexcept { + return __op{ + coro::coroutine_handle::from_promise( + *this)}; + } + coro::suspend_always initial_suspend() noexcept { + return {}; + } + [[noreturn]] coro::suspend_always final_suspend() noexcept { + terminate(); + } + [[noreturn]] void unhandled_exception() noexcept { + terminate(); + } + [[noreturn]] void return_void() noexcept { + terminate(); + } + + template + auto yield_value(Func&& func) noexcept { + struct awaiter { + Func&& func_; + bool await_ready() noexcept { + return false; + } + void await_suspend(coro::coroutine_handle) { + ((Func &&) func_)(); + } + [[noreturn]] void await_resume() noexcept { + terminate(); + } + }; + return awaiter{(Func &&) func}; + } + + // Pass through receiver queries + template<__same_ Self, invocable<__member_t> CPO> + friend auto tag_invoke(CPO cpo, Self&& self) + noexcept(is_nothrow_invocable_v>) + -> invoke_result_t { + return ((CPO&&) cpo)(((Self&&) self).r_); + } + + R& r_; + }; + + coro::coroutine_handle coro_; + + explicit __op(coro::coroutine_handle coro) noexcept + : coro_(coro) {} + + __op(__op&& other) noexcept + : coro_(exchange(other.coro_, {})) {} + + ~__op() { + if (coro_) + coro_.destroy(); + } + + friend void tag_invoke(start_t, __op& self) noexcept { + self.coro_.resume(); + } + }; + } + + inline constexpr struct __fn { + private: + template <__awaitable A, receiver R> + static __impl::__op<__id_t>> __impl(A&& a, R&& r) { + exception_ptr ex; + try { + // This is a bit mind bending control-flow wise. + // We are first evaluating the co_await expression. + // Then the result of that is passed into invoke + // which curries a reference to the result into another + // lambda which is then returned to 'co_yield'. + // The 'co_yield' expression then invokes this lambda + // after the coroutine is suspended so that it is safe + // for the receiver to destroy the coroutine. + auto fn = [&](auto&&... result) { + return [&] { + set_value((R&&) r, (__await_result_t&&) result...); + }; + }; + if constexpr (is_void_v<__await_result_t>) + co_yield (co_await (A &&) a, fn()); + else + co_yield fn(co_await (A &&) a); + } catch (...) { + ex = current_exception(); + } + co_yield [&] { + set_error((R&&) r, (exception_ptr&&) ex); + }; + } + public: + template <__awaitable A, receiver R> + __impl::__op<__id_t>> operator()(A&& a, R&& r) const { + return __impl((A&&) a, (R&&) r); + } + } __connect_awaitable{}; + } + ///////////////////////////////////////////////////////////////////////////// // [execution.senders.connect] inline namespace __connect { @@ -184,6 +317,12 @@ namespace std::execution { noexcept(nothrow_tag_invocable) { return tag_invoke(connect_t{}, (S&&) s, (R&&) r); } + // NOT TO SPEC: + template<__awaitable A, receiver R> + requires (!tag_invocable) + auto operator()(A&& a, R&& r) const { + return __connect_awaitable((A&&) a, (R&&) r); + } } connect {}; } @@ -305,8 +444,9 @@ namespace std::execution { } }; + // NOT TO SPEC: copy_constructible template R> - requires (copyable &&...) + requires (copy_constructible &&...) friend auto tag_invoke(connect_t, const __sender& s, R&& r) noexcept((is_nothrow_copy_constructible_v &&...)) -> __op<__id_t>> { @@ -392,7 +532,7 @@ namespace std::execution { using R = __t; [[no_unique_address]] R r_; [[no_unique_address]] F f_; - + // Customize set_value by invoking the callable and passing the result to the base class template requires invocable && @@ -422,13 +562,13 @@ namespace std::execution { using S = __t; [[no_unique_address]] S s_; [[no_unique_address]] F f_; - + template>> requires sender_to> friend auto tag_invoke(connect_t, __sender&& self, R&& r) noexcept(/*todo*/ false) -> connect_result_t> { - return execution::connect( + return connect( (S&&) self.s_, __receiver{(R&&) r, (F&&) self.f_}); } From f489034d15096408e4a6926075b6443a571e0555 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sat, 21 Aug 2021 19:44:19 -0700 Subject: [PATCH 2/7] works with void-returning awaitables --- examples/Makefile | 2 +- examples/task.hpp | 26 +++++++++++++++++++------- include/execution.hpp | 4 ++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index 7f1de6fbc..401c466ae 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -5,7 +5,7 @@ CXXFLAGS=-I$(IDIR) -std=c++20 -stdlib=libc++ LIBS=-pthread -all: hello_world +all: hello_world hello_coro execution: $(IDIR)/execution.hpp diff --git a/examples/task.hpp b/examples/task.hpp index 52583f819..967d34fd6 100644 --- a/examples/task.hpp +++ b/examples/task.hpp @@ -36,7 +36,13 @@ struct task { void await_resume() const noexcept { } }; - struct promise_type { + struct _promise_base { + void return_value(T value) noexcept { + data_.template emplace<1>(std::move(value)); + } + std::variant data_{}; + }; + struct promise_type : _promise_base { task get_return_object() noexcept { return task(coro::coroutine_handle::from_promise(*this)); } @@ -47,12 +53,8 @@ struct task { return {}; } void unhandled_exception() noexcept { - data_.template emplace<2>(std::current_exception()); - } - void return_value(T value) noexcept { - data_.template emplace<1>(std::move(value)); + this->data_.template emplace<2>(std::current_exception()); } - std::variant data_{}; coro::coroutine_handle<> parent_{}; }; @@ -77,7 +79,8 @@ struct task { T await_resume() const { if (t.coro_.promise().data_.index() == 2) std::rethrow_exception(std::get<2>(t.coro_.promise().data_)); - return std::get(t.coro_.promise().data_); + if constexpr (!std::is_void_v) + return std::get(t.coro_.promise().data_); } }; @@ -91,3 +94,12 @@ struct task { {} coro::coroutine_handle coro_; }; + +template<> +struct task::_promise_base { + struct _void {}; + void return_void() noexcept { + data_.template emplace<1>(_void{}); + } + std::variant data_{}; +}; diff --git a/include/execution.hpp b/include/execution.hpp index eb5d5b9bc..3a58d03b3 100644 --- a/include/execution.hpp +++ b/include/execution.hpp @@ -282,9 +282,9 @@ namespace std::execution { // The 'co_yield' expression then invokes this lambda // after the coroutine is suspended so that it is safe // for the receiver to destroy the coroutine. - auto fn = [&](auto&&... result) { + auto fn = [&](auto&&... r) { return [&] { - set_value((R&&) r, (__await_result_t&&) result...); + set_value((R&&) r, (add_rvalue_reference_t<__await_result_t>) r...); }; }; if constexpr (is_void_v<__await_result_t>) From 10898edae2e98f1c58d11d136b339a822ca1cad0 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sat, 21 Aug 2021 22:29:55 -0700 Subject: [PATCH 3/7] fix typo --- include/execution.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/execution.hpp b/include/execution.hpp index 3a58d03b3..38fcd7827 100644 --- a/include/execution.hpp +++ b/include/execution.hpp @@ -282,9 +282,9 @@ namespace std::execution { // The 'co_yield' expression then invokes this lambda // after the coroutine is suspended so that it is safe // for the receiver to destroy the coroutine. - auto fn = [&](auto&&... r) { + auto fn = [&](auto&&... as) { return [&] { - set_value((R&&) r, (add_rvalue_reference_t<__await_result_t>) r...); + set_value((R&&) r, (add_rvalue_reference_t<__await_result_t>) as...); }; }; if constexpr (is_void_v<__await_result_t>) From 449eb6756e2033cad3649d1e99a7d73d856c2c0b Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 22 Aug 2021 09:09:02 -0700 Subject: [PATCH 4/7] by default, awaitables don't send done --- include/execution.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/execution.hpp b/include/execution.hpp index 38fcd7827..f19f5c01c 100644 --- a/include/execution.hpp +++ b/include/execution.hpp @@ -50,12 +50,13 @@ namespace std::execution { using __unspecialized = void; }; + template struct __void_sender { template class Tuple, template class Variant> using value_types = Variant>; template class Variant> using error_types = Variant; - static constexpr bool sends_done = true; + static constexpr bool sends_done = SendsDone; }; template @@ -84,7 +85,7 @@ namespace std::execution { return sender_base{}; } else if constexpr (__awaitable) { // NOT TO SPEC if constexpr (is_void_v<__await_result_t>) { - return __void_sender{}; + return __void_sender{}; } else { return __sender_of>{}; } @@ -145,7 +146,7 @@ namespace std::execution { concept receiver_of = receiver && requires(remove_cvref_t&& r, An&&... an) { - set_value(std::move(r), (An&&) an...); + set_value((remove_cvref_t&&) r, (An&&) an...); }; template From e282c93a0534649ad8ecce87274276d3d283ec1f Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 22 Aug 2021 11:50:03 -0700 Subject: [PATCH 5/7] prototype extensible, composable sender closures --- include/execution.hpp | 87 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 9 deletions(-) diff --git a/include/execution.hpp b/include/execution.hpp index f19f5c01c..92283c101 100644 --- a/include/execution.hpp +++ b/include/execution.hpp @@ -277,8 +277,8 @@ namespace std::execution { try { // This is a bit mind bending control-flow wise. // We are first evaluating the co_await expression. - // Then the result of that is passed into invoke - // which curries a reference to the result into another + // Then the result of that is passed into a lambda + // that curries a reference to the result into another // lambda which is then returned to 'co_yield'. // The 'co_yield' expression then invokes this lambda // after the coroutine is suspended so that it is safe @@ -509,21 +509,90 @@ namespace std::execution { } execute {}; } + // NOT TO SPEC: + template + extern const bool is_sender_closure; + + template + concept __sender_closure = + (is_sender_closure>) && + move_constructible> && + constructible_from, T>; + namespace __pipe { + template + struct __compose { + [[no_unique_address]] A a_; + [[no_unique_address]] B b_; + + template + requires invocable && invocable> + invoke_result_t> operator()(S&& s) && { + return ((B&&) b_)(((A&&) a_)((S&&) s)); + } + + template + requires invocable && invocable> + invoke_result_t> operator()(S&& s) const & { + return b_(a_((S&&) s)); + } + }; + + struct __sender_closure_tag { + using sender_closure = void; + }; + + template <__sender_closure A, __sender_closure B> + __compose, remove_cvref_t> operator|(A&& a, B&& b) { + return {(A&&) a, (B&&) b}; + } + + template + requires invocable + invoke_result_t operator|(S&& s, C&& c) { + return ((C&&) c)((S&&) s); + } + template - struct __binder { + struct __closure { [[no_unique_address]] Fn fn; tuple as; template - friend decltype(auto) operator|(S&& s, __binder b) { - return std::apply([&](As&... as) { - return ((Fn&&) b.fn)(s, (As&&) as...); - }, b.as); + requires invocable + invoke_result_t operator()(S&& s) && + noexcept(is_nothrow_invocable_v) { + return std::apply([&s, this](As&... as) { + return ((Fn&&) fn)((S&&) s, (As&&) as...); + }, as); + } + + template + requires invocable + invoke_result_t operator()(S&& s) const & + noexcept(is_nothrow_invocable_v) { + return std::apply([&s, this](const As&... as) { + return fn((S&&) s, as...); + }, as); } }; } + // NOT TO SPEC: + using sender_closure_tag = __pipe::__sender_closure_tag; + + template + inline constexpr bool is_sender_closure = + requires { + typename T::sender_closure; + }; + + template + inline constexpr bool is_sender_closure<__pipe::__closure> = true; + + template + inline constexpr bool is_sender_closure<__pipe::__compose> = true; + ///////////////////////////////////////////////////////////////////////////// // [execution.senders.adaptors.then] inline namespace __then { @@ -588,7 +657,7 @@ namespace std::execution { return __impl::__sender<__id_t>, F>{(S&&)s, (F&&)f}; } template - __pipe::__binder operator()(F f) const { + __pipe::__closure operator()(F f) const { return {{}, {(F&&) f}}; } } lazy_then {}; @@ -605,7 +674,7 @@ namespace std::execution { return lazy_then((S&&) s, (F&&) f); } template - __pipe::__binder operator()(F f) const { + __pipe::__closure operator()(F f) const { return {{}, {(F&&) f}}; } } then {}; From e0ecba3fb38c4a86208a2b14643f4e659114f138 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Mon, 23 Aug 2021 10:28:08 -0700 Subject: [PATCH 6/7] refactor connect_awaitable --- include/execution.hpp | 103 ++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/include/execution.hpp b/include/execution.hpp index 92283c101..6c2945268 100644 --- a/include/execution.hpp +++ b/include/execution.hpp @@ -194,11 +194,60 @@ namespace std::execution { // NOT TO SPEC: __connect_awaitable_ inline namespace __connect_awaitable_ { namespace __impl { + struct __op_base { + struct __promise_base { + coro::suspend_always initial_suspend() noexcept { + return {}; + } + [[noreturn]] coro::suspend_always final_suspend() noexcept { + terminate(); + } + [[noreturn]] void unhandled_exception() noexcept { + terminate(); + } + [[noreturn]] void return_void() noexcept { + terminate(); + } + template + auto yield_value(Func&& func) noexcept { + struct awaiter { + Func&& func_; + bool await_ready() noexcept { + return false; + } + void await_suspend(coro::coroutine_handle<>) { + ((Func &&) func_)(); + } + [[noreturn]] void await_resume() noexcept { + terminate(); + } + }; + return awaiter{(Func &&) func}; + } + }; + + coro::coroutine_handle<> coro_; + + explicit __op_base(coro::coroutine_handle<> coro) noexcept + : coro_(coro) {} + + __op_base(__op_base&& other) noexcept + : coro_(exchange(other.coro_, {})) {} + + ~__op_base() { + if (coro_) + coro_.destroy(); + } + + friend void tag_invoke(start_t, __op_base& self) noexcept { + self.coro_.resume(); + } + }; template - class __op { + class __op : public __op_base { using R = __t; public: - struct promise_type { + struct promise_type : __promise_base { template explicit promise_type(A&, R& r) noexcept : r_(r) @@ -206,37 +255,7 @@ namespace std::execution { __op get_return_object() noexcept { return __op{ - coro::coroutine_handle::from_promise( - *this)}; - } - coro::suspend_always initial_suspend() noexcept { - return {}; - } - [[noreturn]] coro::suspend_always final_suspend() noexcept { - terminate(); - } - [[noreturn]] void unhandled_exception() noexcept { - terminate(); - } - [[noreturn]] void return_void() noexcept { - terminate(); - } - - template - auto yield_value(Func&& func) noexcept { - struct awaiter { - Func&& func_; - bool await_ready() noexcept { - return false; - } - void await_suspend(coro::coroutine_handle) { - ((Func &&) func_)(); - } - [[noreturn]] void await_resume() noexcept { - terminate(); - } - }; - return awaiter{(Func &&) func}; + coro::coroutine_handle::from_promise(*this)}; } // Pass through receiver queries @@ -250,22 +269,7 @@ namespace std::execution { R& r_; }; - coro::coroutine_handle coro_; - - explicit __op(coro::coroutine_handle coro) noexcept - : coro_(coro) {} - - __op(__op&& other) noexcept - : coro_(exchange(other.coro_, {})) {} - - ~__op() { - if (coro_) - coro_.destroy(); - } - - friend void tag_invoke(start_t, __op& self) noexcept { - self.coro_.resume(); - } + using __op_base::__op_base; }; } @@ -627,6 +631,7 @@ namespace std::execution { return ((Tag&&) tag)(((Self&&) r).r_, (As&&) as...); } }; + template struct __sender { using S = __t; From 3ce7736b0f7f3be967586f942d4c7c2c0176d802 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Mon, 23 Aug 2021 20:01:31 -0700 Subject: [PATCH 7/7] coro promise mixin to make senders awaitable. doesn't handle done() --- examples/hello_coro.cpp | 11 ++- examples/task.hpp | 3 +- include/execution.hpp | 172 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 174 insertions(+), 12 deletions(-) diff --git a/examples/hello_coro.cpp b/examples/hello_coro.cpp index 7e0a81499..93a2b2fba 100644 --- a/examples/hello_coro.cpp +++ b/examples/hello_coro.cpp @@ -20,12 +20,17 @@ #include "./task.hpp" -task async_answer() { - co_return 42; +using namespace std::execution; + +template +task async_answer(S1 s1, S2 s2) { + // Senders are implicitly awaitable (int this coroutine type): + co_await (S2&&) s2; + co_return co_await (S1&&) s1; } int main() { // Awaitables are implicitly senders: - auto [i] = std::this_thread::sync_wait(async_answer()).value(); + auto [i] = std::this_thread::sync_wait(async_answer(just(42), just())).value(); std::cout << "The answer is " << i << '\n'; } diff --git a/examples/task.hpp b/examples/task.hpp index 967d34fd6..ca158eacb 100644 --- a/examples/task.hpp +++ b/examples/task.hpp @@ -36,13 +36,14 @@ struct task { void await_resume() const noexcept { } }; + // In a base class so it can be specialized when T is void: struct _promise_base { void return_value(T value) noexcept { data_.template emplace<1>(std::move(value)); } std::variant data_{}; }; - struct promise_type : _promise_base { + struct promise_type : _promise_base, std::execution::with_awaitable_senders { task get_return_object() noexcept { return task(coro::coroutine_handle::from_promise(*this)); } diff --git a/include/execution.hpp b/include/execution.hpp index 6c2945268..8fa65be1e 100644 --- a/include/execution.hpp +++ b/include/execution.hpp @@ -15,6 +15,7 @@ */ #pragma once +#include #include #include #include @@ -168,6 +169,25 @@ namespace std::execution { sender && __has_sender_types>>; + template + using __back_t = decltype((static_cast(0),...)()); + template + requires (sizeof...(As) == 1) + using __single_t = __back_t; + template + requires (sizeof...(As) <= 1) + using __single_or_void_t = __back_t; + + template + using __single_sender_result_t = + typename sender_traits> + ::template value_types<__single_or_void_t, __single_t>; + + template + concept __single_typed_sender = + typed_sender && + requires { typename __single_sender_result_t; }; + ///////////////////////////////////////////////////////////////////////////// // [execution.op_state] inline namespace __start { @@ -275,7 +295,7 @@ namespace std::execution { inline constexpr struct __fn { private: - template <__awaitable A, receiver R> + template static __impl::__op<__id_t>> __impl(A&& a, R&& r) { exception_ptr ex; try { @@ -344,6 +364,145 @@ namespace std::execution { template using connect_result_t = tag_invoke_result_t; + ///////////////////////////////////////////////////////////////////////////// + // NOT TO SPEC: as_awaitable and with_awaitable_senders + inline namespace __with_awaitable_senders { + namespace __impl { + struct __void {}; + template + using __value_or_void_t = + conditional_t, __void, Value>; + template + using __expected_t = + variant, std::exception_ptr, set_done_t>; + + template + struct __rec_base { + explicit __rec_base(__expected_t* result, coro::coroutine_handle<> continuation) noexcept + : result_(result) + , continuation_(continuation) + {} + + __rec_base(__rec_base&& r) noexcept + : result_(std::exchange(r.result_, nullptr)) + , continuation_(std::exchange(r.continuation_, nullptr)) + {} + + template + requires constructible_from || + (is_void_v && sizeof...(Us) == 0) + friend void tag_invoke(set_value_t, __rec_base&& self, Us&&... us) + noexcept(is_nothrow_constructible_v || + is_void_v) { + self.result_->template emplace<1>((Us&&) us...); + self.continuation_.resume(); + } + + friend void tag_invoke(set_error_t, __rec_base&& self, exception_ptr eptr) noexcept { + self.result_->template emplace<2>((exception_ptr&&) eptr); + self.continuation_.resume(); + } + + __expected_t* result_; + coro::coroutine_handle<> continuation_; + }; + + template + struct __awaitable_base { + using Promise = __t; + struct __rec : __rec_base { + using __rec_base::__rec_base; + + friend void tag_invoke(set_done_t, __rec&& self) noexcept { + self.result_->template emplace<3>(set_done); + self.continuation_.resume(); // TODO: propose unhandled_done() ? + //self.continuation_.promise().unhandled_done().resume(); + } + + // Forward other tag_invoke overloads to the promise + template <__same_<__rec> Self, class... As, invocable<__member_t&, As...> CPO> + friend auto tag_invoke(CPO cpo, Self&& self, As&&... as) + noexcept(is_nothrow_invocable_v&, As...>) + -> invoke_result_t&, As...> { + auto continuation = coro::coroutine_handle::from_address( + self.continuation_.address()); + __member_t& p = continuation.promise(); + return ((CPO&&) cpo)(p); + } + }; + + bool await_ready() const noexcept { + return false; + } + + Value await_resume() { + switch (result_.index()) { + case 0: + assert(!"Should never get here"); + break; + case 1: // value + if constexpr (!is_void_v) + return (Value&&) std::get<1>(result_); + else + return; + case 2: // exception + std::rethrow_exception(std::get<2>(result_)); + case 3: // done + assert(!"Not implemented yet"); + break; + } + terminate(); + } + + protected: + __expected_t result_; + }; + + template + struct __awaitable + : __awaitable_base>> { + private: + using Promise = __t; + using Sender = __t; + using Base = __awaitable_base>; + using __rec = typename Base::__rec; + connect_result_t op_; + public: + __awaitable(Sender&& sender, coro::coroutine_handle h) + noexcept(/* TODO: is_nothrow_connectable_v*/ false) + : op_(connect((Sender&&)sender, __rec{&this->result_, h})) + {} + + void await_suspend(coro::coroutine_handle) noexcept { + start(op_); + } + }; + } + + inline constexpr struct as_awaitable_t { + template <__single_typed_sender S, class Promise> + auto operator()(S&& s, Promise& promise) const + noexcept(/*TODO*/ false) + -> __impl::__awaitable<__id_t, __id_t>> { + auto h = coro::coroutine_handle::from_promise(promise); + return {(S&&) s, h}; + } + } as_awaitable{}; + + template + struct with_awaitable_senders { + template + decltype(auto) await_transform(Value&& value) { + if constexpr (__awaitable) + return (Value&&) value; + else if constexpr (sender) + return as_awaitable((Value&&) value, static_cast(*this)); + else + return (Value&&) value; + } + }; + } + ///////////////////////////////////////////////////////////////////////////// // [execution.senders.consumer.start_detached] // TODO: turn this into start_detached @@ -414,8 +573,9 @@ namespace std::execution { schedule((S&&) s); }; + // NOT TO SPEC template - using __schedule_result_t = decltype(schedule(std::declval())); + using schedule_result_t = decltype(schedule(std::declval())); ///////////////////////////////////////////////////////////////////////////// // [execution.senders.factories] @@ -494,6 +654,7 @@ namespace std::execution { friend void tag_invoke(set_done_t, __as_receiver&&) noexcept {} }; } + inline constexpr struct execute_t { template requires invocable && move_constructible @@ -714,16 +875,11 @@ namespace std::this_thread { // [execution.senders.consumers.sync_wait] inline namespace __sync_wait { namespace __impl { - template - using __back_t = decltype((static_cast(0),...)()); - template - requires (sizeof...(As) == 1) - using __single_t = __back_t; template using __sync_wait_result_t = typename execution::sender_traits< remove_cvref_t - >::template value_types; + >::template value_types; template struct __state {