diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index acd884c..de068e5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -31,6 +31,8 @@ set(ALL_EXAMPLES if(NOT MSVC) list( APPEND ALL_EXAMPLES + task-sender + alloc-1 bulk c++now-allocator c++now-cancel diff --git a/examples/alloc-1.cpp b/examples/alloc-1.cpp new file mode 100644 index 0000000..129c2a3 --- /dev/null +++ b/examples/alloc-1.cpp @@ -0,0 +1,124 @@ +// examples/alloc-1.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +// ---------------------------------------------------------------------------- +// --- defer_frame turns yields a sender calling a coroutine upon start() --- + +template +struct defer_frame { + Mem mem; + Self self; + template + auto operator()(Arg&&... arg) const { + return ex::let_value(ex::read_env(ex::get_allocator), + [mem = this->mem, self = this->self, ... a = std::forward(arg)](auto alloc) { + return std::invoke(mem, self, std::allocator_arg, alloc, std::move(a)...); + }); + } + template + auto operator()(::std::allocator_arg_t, Alloc alloc, Arg&&... arg) const { + return ex::let_value(ex::just(alloc), + [&mem = this->mem, self = this->self, ... a = std::forward(arg)](auto alloc) { + return std::invoke(mem, self, std::allocator_arg, alloc, std::move(a)...); + }); + } + auto operator()(::std::allocator_arg_t) const = delete; +}; + +template +struct defer_frame { + Task task; + template + auto operator()(Arg&&... arg) const { + return ex::let_value(ex::read_env(ex::get_allocator), + [task = this->task, ... a = std::forward(arg)](auto alloc) { + return std::invoke(task, std::allocator_arg, alloc, std::move(a)...); + }); + } + template + auto operator()(::std::allocator_arg_t, Alloc alloc, Arg&&... arg) const { + return ex::let_value(ex::just(alloc), [&task = this->task, ... a = std::forward(arg)](auto alloc) { + return std::invoke(task, std::allocator_arg, alloc, std::move(a)...); + }); + } + auto operator()(::std::allocator_arg_t) const = delete; +}; + +// ---------------------------------------------------------------------------- + +void* operator new(std::size_t n) { + auto p = std::malloc(n); + std::cout << " global new(" << n << ")->" << p << "\n"; + return p; +} +void operator delete(void* ptr) noexcept { + std::cout << " global operator delete(" << ptr << ")\n"; + std::free(ptr); +} +void operator delete(void* ptr, std::size_t size) noexcept { + std::cout << " global operator delete(" << ptr << ", " << size << ")\n"; + std::free(ptr); +} + +struct resource : std::pmr::memory_resource { + void* do_allocate(std::size_t n, std::size_t) override { + auto p{std::malloc(n)}; + std::cout << " resource::allocate(" << n << ")->" << p << "\n"; + return p; + } + void do_deallocate(void* p, std::size_t n, std::size_t) override { + std::cout << " resource::deallocate(" << p << ", " << n << ")\n"; + std::free(p); + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } +}; + +// ---------------------------------------------------------------------------- + +using allocator_type = std::pmr::polymorphic_allocator; +struct alloc_env { + using allocator_type = ::allocator_type; +}; +template +using a_task = ex::task; + +a_task hidden_async_fun(std::allocator_arg_t, ::allocator_type, int value) { co_return value; } +auto async_fun(int value) { return defer_frame(&hidden_async_fun)(value); } + +int main() { + std::cout << std::unitbuf; + resource res{}; + allocator_type alloc(&res); + + std::cout << "not setting up an allocator:\n"; + ex::sync_wait([]() -> a_task<> { + auto result{co_await async_fun(17)}; + std::cout << " result=" << result << "\n"; + }()); + + std::cout << "setting up an allocator:\n"; + ex::sync_wait(ex::write_env( + []() -> a_task<> { + auto result{co_await async_fun(17)}; + std::cout << " result=" << result << "\n"; + }(), + ex::env{ex::prop{ex::get_allocator, alloc}})); + + std::cout << "setting up an allocator and using defer_frame:\n"; + ex::sync_wait(ex::write_env(defer_frame([](auto, auto) -> a_task<> { + auto result{co_await async_fun(17)}; + std::cout << " result=" << result << "\n"; + })(), + ex::env{ex::prop{ex::get_allocator, alloc}})); +} diff --git a/examples/odd-completions.cpp b/examples/odd-completions.cpp new file mode 100644 index 0000000..efa196a --- /dev/null +++ b/examples/odd-completions.cpp @@ -0,0 +1,28 @@ +// examples/hello.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +namespace ex = beman::execution; + +int main() { + return std::get<0>(ex::sync_wait([]() -> ex::task { + std::cout << "Hello, world!\n"; + co_return co_await ex::just(0); + }()) + .value_or(std::tuple(-1))); + ex::sync_wait([](int value) -> ex::task { + switch (value) { + default: + co_return value; + case -1: + co_yield ex::with_error(std::make_exception_ptr(value)); + case 2: + throw value; + case 0: + co_await ex::just_stopped(); + } + }(0)); +} diff --git a/examples/task-sender.cpp b/examples/task-sender.cpp new file mode 100644 index 0000000..f763bed --- /dev/null +++ b/examples/task-sender.cpp @@ -0,0 +1,160 @@ +// examples/task_sender.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; + +void* operator new(std::size_t n) { + auto p = std::malloc(n); + std::cout << "global new(" << n << ")->" << p << "\n"; + return p; +} +void operator delete(void* ptr) noexcept { + std::cout << " global operator delete(" << ptr << ")\n"; + std::free(ptr); +} +void operator delete(void* ptr, std::size_t size) noexcept { + std::cout << " global operator delete(" << ptr << ", " << size << ")\n"; + std::free(ptr); +} + +template +struct defer_frame { + Mem mem; + Self self; + template + auto operator()(Arg&&... arg) const { + return ex::let_value(ex::read_env(ex::get_allocator), + [mem = this->mem, self = this->self, ... a = std::forward(arg)](auto alloc) { + return std::invoke(mem, self, std::allocator_arg, alloc, std::move(a)...); + }); + } + template + auto operator()(::std::allocator_arg_t, Alloc alloc, Arg&&... arg) const { + return ex::let_value(ex::just(alloc), + [&mem = this->mem, self = this->self, ... a = std::forward(arg)](auto alloc) { + return std::invoke(mem, self, std::allocator_arg, alloc, std::move(a)...); + }); + } + auto operator()(::std::allocator_arg_t) const = delete; +}; + +template +struct defer_frame { + Task task; + template + auto operator()(Arg&&... arg) const { + return ex::let_value(ex::read_env(ex::get_allocator), + [&task = this->task, ... a = std::forward(arg)](auto alloc) { + return std::invoke(task, std::allocator_arg, alloc, std::move(a)...); + }); + } + template + auto operator()(::std::allocator_arg_t, Alloc alloc, Arg&&... arg) const { + return ex::let_value(ex::just(alloc), [&task = this->task, ... a = std::forward(arg)](auto alloc) { + return std::invoke(task, std::allocator_arg, alloc, std::move(a)...); + }); + } + auto operator()(::std::allocator_arg_t) const = delete; +}; + +struct env { + using allocator_type = std::pmr::polymorphic_allocator; +}; + +auto lambda{[](int i, auto&&...) -> ex::task { + auto alloc = co_await ex::read_env(ex::get_allocator); + alloc.deallocate(alloc.allocate(1), 1); + std::cout << "lambda(" << i << ")\n"; + co_return; +}}; + +class example { + ex::task member_(std::allocator_arg_t, std::pmr::polymorphic_allocator, int); + ex::task const_member_(std::allocator_arg_t, std::pmr::polymorphic_allocator, int) const; + + public: + auto member(int i) { return defer_frame(&example::member_, this)(i); } + auto const_member(int i) { return defer_frame(&example::const_member_, this)(i); } +}; + +inline ex::task example::member_(std::allocator_arg_t, std::pmr::polymorphic_allocator, int i) { + std::cout << "example::member(" << i << ")\n"; + co_return; +} +inline ex::task +example::const_member_(std::allocator_arg_t, std::pmr::polymorphic_allocator, int i) const { + std::cout << "example::const member(" << i << ")\n"; + co_return; +} + +struct resource : std::pmr::memory_resource { + void* do_allocate(std::size_t n, std::size_t) override { + auto p{std::malloc(n)}; + std::cout << " resource::allocate(" << n << ")->" << p << "\n"; + return p; + } + void do_deallocate(void* p, std::size_t n, std::size_t) override { + std::cout << " resource::deallocate(" << p << ", " << n << ")\n"; + std::free(p); + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; } +}; + +int main() { + resource res{}; + std::pmr::polymorphic_allocator alloc(&res); + std::cout << "direct allocator use:\n"; + alloc.deallocate(alloc.allocate(1), 1); + std::cout << "write_env/just/then use:\n"; + ex::sync_wait(ex::write_env(ex::just(alloc) | ex::then([](auto a) { a.deallocate(a.allocate(1), 1); }), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/read_env/then use:\n"; + ex::sync_wait( + ex::write_env(ex::read_env(ex::get_allocator) | ex::then([](auto a) { a.deallocate(a.allocate(1), 1); }), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/let_value/then use:\n"; + ex::sync_wait(ex::write_env(ex::just() | ex::let_value([] { + return ex::read_env(ex::get_allocator) | + ex::then([](auto a) { a.deallocate(a.allocate(1), 1); }); + }), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/task<>:\n"; + ex::sync_wait(ex::write_env( + []() -> ex::task<> { + auto a = co_await ex::read_env(ex::get_allocator); + a.deallocate(a.allocate(1), 1); + }(), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/task:\n"; + ex::sync_wait(ex::write_env( + [](auto&&...) -> ex::task { + auto a = co_await ex::read_env(ex::get_allocator); + a.deallocate(a.allocate(1), 1); + }(std::allocator_arg, alloc), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/defer_frame>:\n"; + static constexpr defer_frame t0([](auto, auto, int i) -> ex::task { + std::cout << " i=" << i << "\n"; + auto a = co_await ex::read_env(ex::get_allocator); + a.deallocate(a.allocate(1), 1); + }); + ex::sync_wait(ex::write_env(t0(17), ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "write_env/temporary defer_frame>:\n"; + ex::sync_wait(ex::write_env(defer_frame([](auto, auto, auto i) -> ex::task { + std::cout << " i=" << i << "\n"; + co_await std::suspend_never{}; + auto a = co_await ex::read_env(ex::get_allocator); + a.deallocate(a.allocate(1), 1); + })(42), + ex::env{ex::prop{ex::get_allocator, alloc}})); + std::cout << "done\n"; +} diff --git a/include/beman/task/detail/inline_scheduler.hpp b/include/beman/task/detail/inline_scheduler.hpp index bcdcdf8..916db68 100644 --- a/include/beman/task/detail/inline_scheduler.hpp +++ b/include/beman/task/detail/inline_scheduler.hpp @@ -42,6 +42,10 @@ struct inline_scheduler { struct sender { using sender_concept = ::beman::execution::sender_t; using completion_signatures = ::beman::execution::completion_signatures<::beman::execution::set_value_t()>; + template + static consteval auto get_completion_signatures() noexcept -> completion_signatures { + return {}; + } env get_env() const noexcept { return {}; } template <::beman::execution::receiver Receiver> diff --git a/include/beman/task/detail/promise_type.hpp b/include/beman/task/detail/promise_type.hpp index 0c2f60d..75bdce5 100644 --- a/include/beman/task/detail/promise_type.hpp +++ b/include/beman/task/detail/promise_type.hpp @@ -67,7 +67,7 @@ class promise_type auto get_return_object() noexcept { return Coroutine(::beman::task::detail::handle(this)); } template <::beman::execution::sender Sender> - auto await_transform(Sender&& sender) noexcept { + auto await_transform(Sender&& sender) { if constexpr (requires { ::std::forward(sender).as_awaitable(*this); // typename ::std::remove_cvref_t::task_concept; diff --git a/include/beman/task/detail/state.hpp b/include/beman/task/detail/state.hpp index 567fac0..204d823 100644 --- a/include/beman/task/detail/state.hpp +++ b/include/beman/task/detail/state.hpp @@ -48,8 +48,11 @@ struct state : ::beman::task::detail::state_base, ::beman::task::detail::s return std::noop_coroutine(); } auto do_get_allocator() -> allocator_type override { - if constexpr (requires { ::beman::execution::get_allocator(::beman::execution::get_env(this->receiver)); }) - return ::beman::execution::get_allocator(::beman::execution::get_env(this->receiver)); + if constexpr (requires { + allocator_type( + ::beman::execution::get_allocator(::beman::execution::get_env(this->receiver))); + }) + return allocator_type(::beman::execution::get_allocator(::beman::execution::get_env(this->receiver))); else return allocator_type{}; } diff --git a/include/beman/task/detail/task.hpp b/include/beman/task/detail/task.hpp index 15555c2..5d23b7d 100644 --- a/include/beman/task/detail/task.hpp +++ b/include/beman/task/detail/task.hpp @@ -46,15 +46,19 @@ class task { using stop_token_type = decltype(std::declval().get_token()); public: - using task_concept = int; - using sender_concept = ::beman::execution::sender_t; - using xcompletion_signatures = ::beman::execution::detail::meta::combine< + using task_concept = int; + using sender_concept = ::beman::execution::sender_t; + using completion_signatures = ::beman::execution::detail::meta::combine< ::beman::execution::completion_signatures, ::beman::execution::set_stopped_t()>, ::beman::task::detail::error_types_of_t >; template - auto get_completion_signatures(const Ev&) const& noexcept { - return xcompletion_signatures{}; + auto get_completion_signatures(const Ev&) const& noexcept -> completion_signatures { + return {}; + } + template + static consteval auto get_completion_signatures() noexcept -> completion_signatures { + return {}; } using promise_type = ::beman::task::detail::promise_type; diff --git a/include/beman/task/detail/task_scheduler.hpp b/include/beman/task/detail/task_scheduler.hpp index cc3f127..6bb83be 100644 --- a/include/beman/task/detail/task_scheduler.hpp +++ b/include/beman/task/detail/task_scheduler.hpp @@ -126,6 +126,10 @@ class task_scheduler { public: using sender_concept = ::beman::execution::sender_t; using completion_signatures = ::beman::execution::completion_signatures<::beman::execution::set_value_t()>; + template + static consteval auto get_completion_signatures() noexcept -> completion_signatures { + return {}; + } template <::beman::execution::scheduler S> explicit sender(S&& s) : inner_sender(static_cast*>(nullptr), std::forward(s)) {}