diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-30 07:51:23 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-30 07:51:23 +0100 |
| commit | 034cd404a129103a8dd7747e6bd00ffd5550da93 (patch) | |
| tree | d27946517d4d9333abd26ac50bbd4a436093e2ce /nihil.generator | |
| parent | 3e7902f7d790a486d3d9cb978df193f07f3a6ad9 (diff) | |
| download | nihil-034cd404a129103a8dd7747e6bd00ffd5550da93.tar.gz nihil-034cd404a129103a8dd7747e6bd00ffd5550da93.tar.bz2 | |
refactoring
Diffstat (limited to 'nihil.generator')
| -rw-r--r-- | nihil.generator/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | nihil.generator/byte_allocator.ccm | 27 | ||||
| -rw-r--r-- | nihil.generator/coroutine_traits.ccm | 70 | ||||
| -rw-r--r-- | nihil.generator/forward.ccm | 29 | ||||
| -rw-r--r-- | nihil.generator/generator.ccm | 717 | ||||
| -rw-r--r-- | nihil.generator/generator.test.cc (renamed from nihil.generator/test.cc) | 0 | ||||
| -rw-r--r-- | nihil.generator/generator_promise.ccm | 65 | ||||
| -rw-r--r-- | nihil.generator/generator_promise_base.ccm | 205 | ||||
| -rw-r--r-- | nihil.generator/manual_lifetime.ccm | 8 | ||||
| -rw-r--r-- | nihil.generator/nihil.generator.ccm | 7 |
10 files changed, 680 insertions, 455 deletions
diff --git a/nihil.generator/CMakeLists.txt b/nihil.generator/CMakeLists.txt index 7464785..e521159 100644 --- a/nihil.generator/CMakeLists.txt +++ b/nihil.generator/CMakeLists.txt @@ -6,7 +6,12 @@ target_sources(nihil.generator nihil.generator.ccm generator.ccm + byte_allocator.ccm + coroutine_traits.ccm elements_of.ccm + forward.ccm + generator_promise_base.ccm + generator_promise.ccm manual_lifetime.ccm promise_base_alloc.ccm util.ccm @@ -15,7 +20,7 @@ target_sources(nihil.generator if(NIHIL_TESTS) enable_testing() - add_executable(nihil.generator.test test.cc) + add_executable(nihil.generator.test generator.test.cc) target_link_libraries(nihil.generator.test PRIVATE nihil.generator Catch2::Catch2WithMain diff --git a/nihil.generator/byte_allocator.ccm b/nihil.generator/byte_allocator.ccm new file mode 100644 index 0000000..6d46ec6 --- /dev/null +++ b/nihil.generator/byte_allocator.ccm @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////// +// Reference implementation of std::generator proposal P2168. +// +// See https://wg21.link/P2168 for details. +// +/////////////////////////////////////////////////////////////////////////////// +// Copyright Lewis Baker, Corentin Jabot +// +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. +// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) +/////////////////////////////////////////////////////////////////////////////// + +module; + +#include <memory> +#include <type_traits> + +export module nihil.generator:byte_allocator; + +namespace nihil { + +template <typename Alloc> +using byte_allocator_t = typename std::allocator_traits< + std::remove_cvref_t<Alloc>>::template rebind_alloc<std::byte>; + +} // namespace nihil diff --git a/nihil.generator/coroutine_traits.ccm b/nihil.generator/coroutine_traits.ccm new file mode 100644 index 0000000..2a9d51d --- /dev/null +++ b/nihil.generator/coroutine_traits.ccm @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////// +// Reference implementation of std::generator proposal P2168. +// +// See https://wg21.link/P2168 for details. +// +/////////////////////////////////////////////////////////////////////////////// +// Copyright Lewis Baker, Corentin Jabot +// +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. +// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) +/////////////////////////////////////////////////////////////////////////////// + +module; + +#include <coroutine> +#include <memory> + +export module nihil.generator:coroutine_traits; + +import :byte_allocator; +import :forward; +import :generator_promise; + +namespace std { + +// Type-erased allocator with default allocator behaviour. +export template <typename Ref, typename Value, typename... Args> +struct coroutine_traits<nihil::generator<Ref, Value>, Args...> +{ + using promise_type = + nihil::generator_promise<nihil::generator<Ref, Value>, std::allocator<std::byte>>; +}; + +// Type-erased allocator with std::allocator_arg parameter +export template <typename Ref, typename Value, typename Alloc, typename... Args> +struct coroutine_traits<nihil::generator<Ref, Value>, allocator_arg_t, Alloc, Args...> +{ +private: + using byte_allocator = nihil::byte_allocator_t<Alloc>; + +public: + using promise_type = nihil::generator_promise<nihil::generator<Ref, Value>, byte_allocator, + true /*explicit Allocator*/>; +}; + +// Type-erased allocator with std::allocator_arg parameter (non-static member functions) +export template <typename Ref, typename Value, typename This, typename Alloc, typename... Args> +struct coroutine_traits<nihil::generator<Ref, Value>, This, allocator_arg_t, Alloc, Args...> +{ +private: + using byte_allocator = nihil::byte_allocator_t<Alloc>; + +public: + using promise_type = nihil::generator_promise<nihil::generator<Ref, Value>, byte_allocator, + true /*explicit Allocator*/>; +}; + +// Generator with specified allocator type +export template <typename Ref, typename Value, typename Alloc, typename... Args> +struct coroutine_traits<nihil::generator<Ref, Value, Alloc>, Args...> +{ + using byte_allocator = nihil::byte_allocator_t<Alloc>; + +public: + using promise_type = + nihil::generator_promise<nihil::generator<Ref, Value, Alloc>, byte_allocator>; +}; + +} // namespace std diff --git a/nihil.generator/forward.ccm b/nihil.generator/forward.ccm new file mode 100644 index 0000000..8d5ca4d --- /dev/null +++ b/nihil.generator/forward.ccm @@ -0,0 +1,29 @@ +/////////////////////////////////////////////////////////////////////////////// +// Reference implementation of std::generator proposal P2168. +// +// See https://wg21.link/P2168 for details. +// +/////////////////////////////////////////////////////////////////////////////// +// Copyright Lewis Baker, Corentin Jabot +// +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. +// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) +/////////////////////////////////////////////////////////////////////////////// + +module; + +#include <type_traits> + +export module nihil.generator:forward; + +import :util; + +namespace nihil { + +export template <typename Ref, + typename Value = std::remove_cvref_t<Ref>, + typename Allocator = use_allocator_arg> +class generator; + +} // namespace nihil diff --git a/nihil.generator/generator.ccm b/nihil.generator/generator.ccm index 27e8103..96790a8 100644 --- a/nihil.generator/generator.ccm +++ b/nihil.generator/generator.ccm @@ -23,485 +23,306 @@ module; export module nihil.generator:generator; +import :byte_allocator; +import :coroutine_traits; import :elements_of; +import :forward; +import :generator_promise_base; +import :generator_promise; import :manual_lifetime; import :promise_base_alloc; import :util; namespace nihil { -export template <typename _Ref, - typename _Value = std::remove_cvref_t<_Ref>, - typename _Allocator = use_allocator_arg> -class generator; - - -template<typename _Ref> -struct __generator_promise_base +// TODO : make layout compatible promise casts possible +export template <typename Ref, typename Value, typename Alloc> +class generator { - template <typename _Ref2, typename _Value, typename _Alloc> - friend class generator; - - __generator_promise_base* __root_; - std::coroutine_handle<> __parentOrLeaf_; - // Note: Using manual_lifetime here to avoid extra calls to exception_ptr - // constructor/destructor in cases where it is not needed (i.e. where this - // generator coroutine is not used as a nested coroutine). - // This member is lazily constructed by the __yield_sequence_awaiter::await_suspend() - // method if this generator is used as a nested generator. - manual_lifetime<std::exception_ptr> __exception_; - manual_lifetime<_Ref> __value_; - - explicit __generator_promise_base(std::coroutine_handle<> thisCoro) noexcept - : __root_(this) - , __parentOrLeaf_(thisCoro) - {} - - ~__generator_promise_base() { - if (__root_ != this) { - // This coroutine was used as a nested generator and so will - // have constructed its __exception_ member which needs to be - // destroyed here. - __exception_.destruct(); - } - } - - std::suspend_always initial_suspend() noexcept { - return {}; - } - - void return_void() noexcept {} - - void unhandled_exception() { - if (__root_ != this) { - __exception_.get() = std::current_exception(); - } else { - throw; - } - } - - // Transfers control back to the parent of a nested coroutine - struct __final_awaiter { - bool await_ready() noexcept { - return false; - } - - template <typename _Promise> - std::coroutine_handle<> - await_suspend(std::coroutine_handle<_Promise> __h) noexcept { - _Promise& __promise = __h.promise(); - __generator_promise_base& __root = *__promise.__root_; - if (&__root != &__promise) { - auto __parent = __promise.__parentOrLeaf_; - __root.__parentOrLeaf_ = __parent; - return __parent; - } - return std::noop_coroutine(); - } - - void await_resume() noexcept {} - }; - - __final_awaiter final_suspend() noexcept { - return {}; - } - - std::suspend_always yield_value(_Ref&& __x) - noexcept(std::is_nothrow_move_constructible_v<_Ref>) { - __root_->__value_.construct((_Ref&&)__x); - return {}; - } - - template <typename _T> - requires - (!std::is_reference_v<_Ref>) && - std::is_convertible_v<_T, _Ref> - std::suspend_always yield_value(_T&& __x) - noexcept(std::is_nothrow_constructible_v<_Ref, _T>) { - __root_->__value_.construct((_T&&)__x); - return {}; - } - - template <typename _Gen> - struct __yield_sequence_awaiter { - _Gen __gen_; - - __yield_sequence_awaiter(_Gen&& __g) noexcept - // Taking ownership of the generator ensures frame are destroyed - // in the reverse order of their execution. - : __gen_((_Gen&&)__g) { - } - - bool await_ready() noexcept { - return false; - } - - // set the parent, root and exceptions pointer and - // resume the nested - template<typename _Promise> - std::coroutine_handle<> - await_suspend(std::coroutine_handle<_Promise> __h) noexcept { - __generator_promise_base& __current = __h.promise(); - __generator_promise_base& __nested = *__gen_.__get_promise(); - __generator_promise_base& __root = *__current.__root_; - - __nested.__root_ = __current.__root_; - __nested.__parentOrLeaf_ = __h; - - // Lazily construct the __exception_ member here now that we - // know it will be used as a nested generator. This will be - // destroyed by the promise destructor. - __nested.__exception_.construct(); - __root.__parentOrLeaf_ = __gen_.__get_coro(); - - // Immediately resume the nested coroutine (nested generator) - return __gen_.__get_coro(); - } - - void await_resume() { - __generator_promise_base& __nestedPromise = *__gen_.__get_promise(); - if (__nestedPromise.__exception_.get()) { - std::rethrow_exception(std::move(__nestedPromise.__exception_.get())); - } - } - }; - - template <typename _OValue, typename _OAlloc> - __yield_sequence_awaiter<generator<_Ref, _OValue, _OAlloc>> - yield_value(nihil::elements_of<generator<_Ref, _OValue, _OAlloc>> __g) noexcept { - return std::move(__g).get(); - } - - template <std::ranges::range _Rng, typename _Allocator> - __yield_sequence_awaiter<generator<_Ref, std::remove_cvref_t<_Ref>, _Allocator>> - yield_value(nihil::elements_of<_Rng, _Allocator> && __x) { - return [](std::allocator_arg_t, _Allocator, auto && __rng) -> generator<_Ref, std::remove_cvref_t<_Ref>, _Allocator> { - for(auto && e: __rng) - co_yield static_cast<decltype(e)>(e); - }(std::allocator_arg, __x.get_allocator(), std::forward<_Rng>(__x.get())); - } - - void resume() { - __parentOrLeaf_.resume(); - } - - // Disable use of co_await within this coroutine. - void await_transform() = delete; -}; + using byte_allocator = byte_allocator_t<Alloc>; -template<typename _Generator, typename _ByteAllocator, bool _ExplicitAllocator = false> -struct __generator_promise; - -template<typename _Ref, typename _Value, typename _Alloc, typename _ByteAllocator, bool _ExplicitAllocator> -struct __generator_promise<generator<_Ref, _Value, _Alloc>, _ByteAllocator, _ExplicitAllocator> final - : public __generator_promise_base<_Ref> - , public promise_base_alloc<_ByteAllocator> { - __generator_promise() noexcept - : __generator_promise_base<_Ref>(std::coroutine_handle<__generator_promise>::from_promise(*this)) - {} - - generator<_Ref, _Value, _Alloc> get_return_object() noexcept { - return generator<_Ref, _Value, _Alloc>{ - std::coroutine_handle<__generator_promise>::from_promise(*this) - }; - } - - using __generator_promise_base<_Ref>::yield_value; - - template <std::ranges::range _Rng> - typename __generator_promise_base<_Ref>::template __yield_sequence_awaiter<generator<_Ref, _Value, _Alloc>> - yield_value(nihil::elements_of<_Rng> && __x) { - static_assert (!_ExplicitAllocator, - "This coroutine has an explicit allocator specified with std::allocator_arg so an allocator needs to be passed " - "explicitely to std::elements_of"); - return [](auto && __rng) -> generator<_Ref, _Value, _Alloc> { - for(auto && e: __rng) - co_yield static_cast<decltype(e)>(e); - }(std::forward<_Rng>(__x.get())); - } -}; - -template<typename _Alloc> -using __byte_allocator_t = typename std::allocator_traits<std::remove_cvref_t<_Alloc>>::template rebind_alloc<std::byte>; - -} // namespace nihil - -namespace std { - -// Type-erased allocator with default allocator behaviour. -export template<typename _Ref, typename _Value, typename... _Args> -struct coroutine_traits<nihil::generator<_Ref, _Value>, _Args...> { - using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, std::allocator<std::byte>>; -}; - -// Type-erased allocator with std::allocator_arg parameter -export template<typename _Ref, typename _Value, typename _Alloc, typename... _Args> -struct coroutine_traits<nihil::generator<_Ref, _Value>, allocator_arg_t, _Alloc, _Args...> { -private: - using __byte_allocator = nihil::__byte_allocator_t<_Alloc>; public: - using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, __byte_allocator, true /*explicit Allocator*/>; -}; + using promise_type = generator_promise<generator<Ref, Value, Alloc>, byte_allocator>; + friend promise_type; -// Type-erased allocator with std::allocator_arg parameter (non-static member functions) -export template<typename _Ref, typename _Value, typename _This, typename _Alloc, typename... _Args> -struct coroutine_traits<nihil::generator<_Ref, _Value>, _This, allocator_arg_t, _Alloc, _Args...> { private: - using __byte_allocator = nihil::__byte_allocator_t<_Alloc>; -public: - using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, __byte_allocator, true /*explicit Allocator*/>; -}; + using coroutine_handle = std::coroutine_handle<promise_type>; -// Generator with specified allocator type -export template<typename _Ref, typename _Value, typename _Alloc, typename... _Args> -struct coroutine_traits<nihil::generator<_Ref, _Value, _Alloc>, _Args...> { - using __byte_allocator = nihil::__byte_allocator_t<_Alloc>; public: - using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value, _Alloc>, __byte_allocator>; -}; - -} // namespace std - -namespace nihil { + generator() noexcept = default; + + generator(generator &&other) noexcept + : m_coro(std::exchange(other.m_coro, {})) + , m_started(std::exchange(other.m_started, false)) + { + } + + ~generator() noexcept + { + if (m_coro) { + if (m_started && !m_coro.done()) { + m_coro.promise().__value_.destruct(); + } + m_coro.destroy(); + } + } + + auto operator=(generator &&g) noexcept -> generator & + { + swap(g); + return *this; + } + + void swap(generator &other) noexcept + { + std::swap(m_coro, other.m_coro); + std::swap(m_started, other.m_started); + } + + struct sentinel + { + }; + + struct iterator + { + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Value; + using reference = Ref; + using pointer = std::add_pointer_t<Ref>; + + iterator() noexcept = default; + iterator(const iterator &) = delete; + + iterator(iterator &&other) noexcept + : m_coro(std::exchange(other.m_coro, {})) + { + } + + auto operator=(iterator &&other) noexcept -> iterator & + { + std::swap(m_coro, other.m_coro); + return *this; + } + + ~iterator() = default; + + friend auto operator==(const iterator &it, sentinel) noexcept -> bool + { + return it.m_coro.done(); + } + + auto operator++() -> iterator & + { + m_coro.promise().m_value.destruct(); + m_coro.promise().resume(); + return *this; + } + + void operator++(int) + { + (void)operator++(); + } + + auto operator*() const noexcept -> reference + { + return static_cast<reference>(m_coro.promise().m_value.get()); + } + + private: + friend generator; + + explicit iterator(coroutine_handle coro) noexcept + : m_coro(coro) + { + } + + coroutine_handle m_coro; + }; + + auto begin() -> iterator + { + assert(m_coro); + assert(!m_started); + m_started = true; + m_coro.resume(); + return iterator{m_coro}; + } + + auto end() noexcept -> sentinel + { + return {}; + } -// TODO : make layout compatible promise casts possible -export template <typename _Ref, typename _Value, typename _Alloc> -class generator { - using __byte_allocator = __byte_allocator_t<_Alloc>; -public: - using promise_type = __generator_promise<generator<_Ref, _Value, _Alloc>, __byte_allocator>; - friend promise_type; private: - using __coroutine_handle = std::coroutine_handle<promise_type>; -public: - - generator() noexcept = default; - - generator(generator&& __other) noexcept - : __coro_(std::exchange(__other.__coro_, {})) - , __started_(std::exchange(__other.__started_, false)) { - } - - ~generator() noexcept { - if (__coro_) { - if (__started_ && !__coro_.done()) { - __coro_.promise().__value_.destruct(); - } - __coro_.destroy(); - } - } - - generator& operator=(generator && g) noexcept { - swap(g); - return *this; - } - - void swap(generator& __other) noexcept { - std::swap(__coro_, __other.__coro_); - std::swap(__started_, __other.__started_); - } - - struct sentinel {}; - - class iterator { - public: - using iterator_category = std::input_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = _Value; - using reference = _Ref; - using pointer = std::add_pointer_t<_Ref>; - - iterator() noexcept = default; - iterator(const iterator &) = delete; - - iterator(iterator&& __other) noexcept - : __coro_(std::exchange(__other.__coro_, {})) { - } - - iterator& operator=(iterator&& __other) { - std::swap(__coro_, __other.__coro_); - return *this; - } - - ~iterator() { - } - - friend bool operator==(const iterator &it, sentinel) noexcept { - return it.__coro_.done(); - } - - iterator &operator++() { - __coro_.promise().__value_.destruct(); - __coro_.promise().resume(); - return *this; - } - void operator++(int) { - (void)operator++(); - } - - reference operator*() const noexcept { - return static_cast<reference>(__coro_.promise().__value_.get()); - } - - private: - friend generator; - - explicit iterator(__coroutine_handle __coro) noexcept - : __coro_(__coro) {} - - __coroutine_handle __coro_; - }; - - iterator begin() { - assert(__coro_); - assert(!__started_); - __started_ = true; - __coro_.resume(); - return iterator{__coro_}; - } - - sentinel end() noexcept { - return {}; - } - -private: - explicit generator(__coroutine_handle __coro) noexcept - : __coro_(__coro) { - } + explicit generator(coroutine_handle coro) noexcept + : m_coro(coro) + { + } public: // to get around access restrictions for __yield_sequence_awaitable - std::coroutine_handle<> __get_coro() noexcept { return __coro_; } - promise_type* __get_promise() noexcept { return std::addressof(__coro_.promise()); } + auto get_coro() noexcept -> std::coroutine_handle<> + { + return m_coro; + } + auto get_promise() noexcept -> promise_type * + { + return std::addressof(m_coro.promise()); + } private: - __coroutine_handle __coro_; - bool __started_ = false; + coroutine_handle m_coro; + bool m_started = false; }; // Specialisation for type-erased allocator implementation. -export template <typename _Ref, typename _Value> -class generator<_Ref, _Value, use_allocator_arg> { - using __promise_base = __generator_promise_base<_Ref>; -public: +export template <typename Ref, typename Value> +class generator<Ref, Value, use_allocator_arg> +{ + using promise_base = generator_promise_base<Ref>; - generator() noexcept - : __promise_(nullptr) - , __coro_() - , __started_(false) - {} - - generator(generator&& __other) noexcept - : __promise_(std::exchange(__other.__promise_, nullptr)) - , __coro_(std::exchange(__other.__coro_, {})) - , __started_(std::exchange(__other.__started_, false)) { - } - - ~generator() noexcept { - if (__coro_) { - if (__started_ && !__coro_.done()) { - __promise_->__value_.destruct(); - } - __coro_.destroy(); - } - } - - generator& operator=(generator g) noexcept { - swap(g); - return *this; - } - - void swap(generator& __other) noexcept { - std::swap(__promise_, __other.__promise_); - std::swap(__coro_, __other.__coro_); - std::swap(__started_, __other.__started_); - } - - struct sentinel {}; - - class iterator { - public: - using iterator_category = std::input_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = _Value; - using reference = _Ref; - using pointer = std::add_pointer_t<_Ref>; - - iterator() noexcept = default; - iterator(const iterator &) = delete; - - iterator(iterator&& __other) noexcept - : __promise_(std::exchange(__other.__promise_, nullptr)) - , __coro_(std::exchange(__other.__coro_, {})) - {} - - iterator& operator=(iterator&& __other) { - __promise_ = std::exchange(__other.__promise_, nullptr); - __coro_ = std::exchange(__other.__coro_, {}); - return *this; - } - - ~iterator() = default; - - friend bool operator==(const iterator &it, sentinel) noexcept { - return it.__coro_.done(); - } - - iterator& operator++() { - __promise_->__value_.destruct(); - __promise_->resume(); - return *this; - } - - void operator++(int) { - (void)operator++(); - } - - reference operator*() const noexcept { - return static_cast<reference>(__promise_->__value_.get()); - } - - private: - friend generator; - - explicit iterator(__promise_base* __promise, std::coroutine_handle<> __coro) noexcept - : __promise_(__promise) - , __coro_(__coro) - {} - - __promise_base* __promise_; - std::coroutine_handle<> __coro_; - }; - - iterator begin() { - assert(__coro_); - assert(!__started_); - __started_ = true; - __coro_.resume(); - return iterator{__promise_, __coro_}; - } - - sentinel end() noexcept { - return {}; - } +public: + generator() noexcept + : m_promise(nullptr) + { + } + + generator(generator &&other) noexcept + : m_promise(std::exchange(other.m_promise, nullptr)) + , m_coro(std::exchange(other.m_coro, {})) + , m_started(std::exchange(other.m_started, false)) + { + } + + ~generator() noexcept + { + if (m_coro) { + if (m_started && !m_coro.done()) { + m_promise->m_value.destruct(); + } + m_coro.destroy(); + } + } + + auto operator=(generator g) noexcept -> generator & + { + swap(g); + return *this; + } + + void swap(generator &other) noexcept + { + std::swap(m_promise, other.m_promise); + std::swap(m_coro, other.m_coro); + std::swap(m_started, other.m_started); + } + + struct sentinel + { + }; + + struct iterator + { + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Value; + using reference = Ref; + using pointer = std::add_pointer_t<Ref>; + + iterator() noexcept = default; + iterator(const iterator &) = delete; + + iterator(iterator &&other) noexcept + : m_promise(std::exchange(other.m_promise, nullptr)) + , m_coro(std::exchange(other.m_coro, {})) + { + } + + auto operator=(iterator &&other) noexcept -> iterator & + { + m_promise = std::exchange(other.m_promise, nullptr); + m_coro = std::exchange(other.m_coro, {}); + return *this; + } + + ~iterator() = default; + + friend auto operator==(const iterator &it, sentinel) noexcept -> bool + { + return it.m_coro.done(); + } + + auto operator++() -> iterator & + { + m_promise->m_value.destruct(); + m_promise->resume(); + return *this; + } + + void operator++(int) + { + (void)operator++(); + } + + auto operator*() const noexcept -> reference + { + return static_cast<reference>(m_promise->m_value.get()); + } + + private: + friend generator; + + explicit iterator(promise_base *promise, + std::coroutine_handle<> coro) noexcept + : m_promise(promise) + , m_coro(coro) + { + } + + promise_base *m_promise; + std::coroutine_handle<> m_coro; + }; + + auto begin() -> iterator + { + assert(m_coro); + assert(!m_started); + m_started = true; + m_coro.resume(); + return iterator{m_promise, m_coro}; + } + + auto end() noexcept -> sentinel + { + return {}; + } private: - template<typename _Generator, typename _ByteAllocator, bool _ExplicitAllocator> - friend struct __generator_promise; + template <typename Generator, typename ByteAllocator, bool ExplicitAllocator> + friend struct generator_promise; - template<typename _Promise> - explicit generator(std::coroutine_handle<_Promise> __coro) noexcept - : __promise_(std::addressof(__coro.promise())) - , __coro_(__coro) - {} + template <typename Promise> + explicit generator(std::coroutine_handle<Promise> coro) noexcept + : m_promise(std::addressof(coro.promise())) + , m_coro(coro) + { + } public: // to get around access restrictions for __yield_sequence_awaitable - std::coroutine_handle<> __get_coro() noexcept { return __coro_; } - __promise_base* __get_promise() noexcept { return __promise_; } + auto get_coro() noexcept -> std::coroutine_handle<> + { + return m_coro; + } + + auto get_promise() noexcept -> promise_base * + { + return m_promise; + } private: - __promise_base* __promise_; - std::coroutine_handle<> __coro_; - bool __started_ = false; + promise_base *m_promise; + std::coroutine_handle<> m_coro; + bool m_started = false; }; } // namespace nihil diff --git a/nihil.generator/test.cc b/nihil.generator/generator.test.cc index 49272b4..49272b4 100644 --- a/nihil.generator/test.cc +++ b/nihil.generator/generator.test.cc diff --git a/nihil.generator/generator_promise.ccm b/nihil.generator/generator_promise.ccm new file mode 100644 index 0000000..b0fd4b1 --- /dev/null +++ b/nihil.generator/generator_promise.ccm @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////// +// Reference implementation of std::generator proposal P2168. +// +// See https://wg21.link/P2168 for details. +// +/////////////////////////////////////////////////////////////////////////////// +// Copyright Lewis Baker, Corentin Jabot +// +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. +// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) +/////////////////////////////////////////////////////////////////////////////// + +module; + +#include <coroutine> +#include <ranges> + +export module nihil.generator:generator_promise; + +import :forward; +import :generator_promise_base; +import :promise_base_alloc; + +namespace nihil { + +export template <typename Generator, typename ByteAllocator, bool ExplicitAllocator = false> +struct generator_promise; + +export template <typename Ref, typename Value, typename Alloc, typename ByteAllocator, + bool ExplicitAllocator> +struct generator_promise<generator<Ref, Value, Alloc>, ByteAllocator, ExplicitAllocator> final + : public generator_promise_base<Ref>, + public promise_base_alloc<ByteAllocator> +{ + generator_promise() noexcept + : generator_promise_base<Ref>( + std::coroutine_handle<generator_promise>::from_promise(*this)) + { + } + + auto get_return_object() noexcept -> generator<Ref, Value, Alloc> + { + return generator<Ref, Value, Alloc>{ + std::coroutine_handle<generator_promise>::from_promise(*this)}; + } + + using generator_promise_base<Ref>::yield_value; + + template <std::ranges::range Rng> + auto yield_value(nihil::elements_of<Rng> &&x) -> typename generator_promise_base< + Ref>::template yield_sequence_awaiter<generator<Ref, Value, Alloc>> + { + static_assert(!ExplicitAllocator, + "This coroutine has an explicit allocator specified with " + "std::allocator_arg so an allocator needs to be passed " + "explicitely to std::elements_of"); + return [](auto &&rng) -> generator<Ref, Value, Alloc> { + for (auto &&e : rng) + co_yield static_cast<decltype(e)>(e); + }(std::forward<Rng>(x.get())); + } +}; + +} // namespace nihil diff --git a/nihil.generator/generator_promise_base.ccm b/nihil.generator/generator_promise_base.ccm new file mode 100644 index 0000000..fec9b1b --- /dev/null +++ b/nihil.generator/generator_promise_base.ccm @@ -0,0 +1,205 @@ +/////////////////////////////////////////////////////////////////////////////// +// Reference implementation of std::generator proposal P2168. +// +// See https://wg21.link/P2168 for details. +// +/////////////////////////////////////////////////////////////////////////////// +// Copyright Lewis Baker, Corentin Jabot +// +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. +// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) +/////////////////////////////////////////////////////////////////////////////// + +module; + +#include <coroutine> +#include <exception> +#include <memory> + +export module nihil.generator:generator_promise_base; + +import :elements_of; +import :forward; +import :manual_lifetime; + +namespace nihil { + +template <typename Ref> +struct generator_promise_base +{ + template <typename Ref2, typename Value, typename Alloc> + friend class generator; + + generator_promise_base *m_root; + std::coroutine_handle<> m_parent_or_leaf; + + // Note: Using manual_lifetime here to avoid extra calls to exception_ptr + // constructor/destructor in cases where it is not needed (i.e. where this + // generator coroutine is not used as a nested coroutine). + // This member is lazily constructed by the __yield_sequence_awaiter::await_suspend() + // method if this generator is used as a nested generator. + manual_lifetime<std::exception_ptr> m_exception; + manual_lifetime<Ref> m_value; + + explicit generator_promise_base(std::coroutine_handle<> this_coro) noexcept + : m_root(this) + , m_parent_or_leaf(this_coro) + { + } + + ~generator_promise_base() + { + if (m_root != this) { + // This coroutine was used as a nested generator and so will + // have constructed its __exception_ member which needs to be + // destroyed here. + m_exception.destruct(); + } + } + + auto initial_suspend() noexcept -> std::suspend_always + { + return {}; + } + + auto return_void() noexcept -> void + { + } + + auto unhandled_exception() -> void + { + if (m_root != this) + m_exception.get() = std::current_exception(); + else + throw; + } + + // Transfers control back to the parent of a nested coroutine + struct final_awaiter + { + auto await_ready() noexcept -> bool + { + return false; + } + + template <typename Promise> + auto + await_suspend(std::coroutine_handle<Promise> h) noexcept -> std::coroutine_handle<> + { + Promise &promise = h.promise(); + generator_promise_base &root = *promise.m_root; + if (&root != &promise) { + auto parent = promise.m_parent_or_leaf; + root.m_parent_or_leaf = parent; + return parent; + } + return std::noop_coroutine(); + } + + void await_resume() noexcept + { + } + }; + + auto final_suspend() noexcept -> final_awaiter + { + return {}; + } + + auto yield_value(Ref &&x) noexcept(std::is_nothrow_move_constructible_v<Ref>) + -> std::suspend_always + { + m_root->m_value.construct((Ref &&)x); + return {}; + } + + template <typename T> + requires(!std::is_reference_v<Ref>) && std::is_convertible_v<T, Ref> + auto + yield_value(T &&x) noexcept(std::is_nothrow_constructible_v<Ref, T>) -> std::suspend_always + { + m_root->m_value.construct((T &&)x); + return {}; + } + + template <typename Gen> + struct yield_sequence_awaiter + { + Gen m_gen; + + yield_sequence_awaiter(Gen &&g) noexcept + // Taking ownership of the generator ensures frame are destroyed + // in the reverse order of their execution. + : m_gen((Gen &&)g) + { + } + + auto await_ready() noexcept -> bool + { + return false; + } + + // set the parent, root and exceptions pointer and + // resume the nested + template <typename Promise> + auto + await_suspend(std::coroutine_handle<Promise> h) noexcept -> std::coroutine_handle<> + { + generator_promise_base ¤t = h.promise(); + generator_promise_base &nested = *m_gen.get_promise(); + generator_promise_base &root = *current.m_root; + + nested.m_root = current.m_root; + nested.m_parent_or_leaf = h; + + // Lazily construct the __exception_ member here now that we + // know it will be used as a nested generator. This will be + // destroyed by the promise destructor. + nested.m_exception.construct(); + root.m_parent_or_leaf = m_gen.get_coro(); + + // Immediately resume the nested coroutine (nested generator) + return m_gen.get_coro(); + } + + void await_resume() + { + generator_promise_base &nested_promise = *m_gen.get_promise(); + + if (nested_promise.m_exception.get()) { + std::rethrow_exception(nested_promise.m_exception.get()); + } + } + }; + + template <typename OValue, typename OAlloc> + auto yield_value(nihil::elements_of<generator<Ref, OValue, OAlloc>> g) noexcept + -> yield_sequence_awaiter<generator<Ref, OValue, OAlloc>> + + { + return std::move(g).get(); + } + + template <std::ranges::range Rng, typename Allocator> + auto yield_value(nihil::elements_of<Rng, Allocator> &&x) + -> yield_sequence_awaiter<generator<Ref, std::remove_cvref_t<Ref>, Allocator>> + + { + return [](std::allocator_arg_t, Allocator, + auto &&rng) -> generator<Ref, std::remove_cvref_t<Ref>, Allocator> { + for (auto &&e : rng) + co_yield static_cast<decltype(e)>(e); + }(std::allocator_arg, x.get_allocator(), std::forward<Rng>(x.get())); + } + + void resume() + { + m_parent_or_leaf.resume(); + } + + // Disable use of co_await within this coroutine. + void await_transform() = delete; +}; + +} // namespace nihil diff --git a/nihil.generator/manual_lifetime.ccm b/nihil.generator/manual_lifetime.ccm index d249e99..963e6c9 100644 --- a/nihil.generator/manual_lifetime.ccm +++ b/nihil.generator/manual_lifetime.ccm @@ -68,8 +68,8 @@ private: template <typename T> class manual_lifetime<T &> { - manual_lifetime() noexcept {} - ~manual_lifetime() {} + manual_lifetime() noexcept = default; + ~manual_lifetime() = default; auto construct(this manual_lifetime &self, T &value) noexcept -> T & { @@ -92,8 +92,8 @@ private: template <typename T> class manual_lifetime<T &&> { - manual_lifetime() noexcept {} - ~manual_lifetime() {} + manual_lifetime() noexcept = default; + ~manual_lifetime() = default; auto construct(this manual_lifetime &self, T &&value) noexcept -> T && { diff --git a/nihil.generator/nihil.generator.ccm b/nihil.generator/nihil.generator.ccm index 9eec5b4..ac9076b 100644 --- a/nihil.generator/nihil.generator.ccm +++ b/nihil.generator/nihil.generator.ccm @@ -17,16 +17,19 @@ module; export module nihil.generator; +export import :byte_allocator; +export import :coroutine_traits; export import :elements_of; +export import :forward; export import :generator; +export import :generator_promise_base; export import :manual_lifetime; export import :promise_base_alloc; export import :util; -export namespace std::ranges { +export namespace std::ranges { // NOLINT template <typename T, typename U, typename Alloc> constexpr inline bool enable_view<nihil::generator<T, U, Alloc>> = true; } // namespace std::ranges - |
