aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.generator
diff options
context:
space:
mode:
Diffstat (limited to 'nihil.generator')
-rw-r--r--nihil.generator/CMakeLists.txt29
-rw-r--r--nihil.generator/elements_of.ccm69
-rw-r--r--nihil.generator/generator.ccm507
-rw-r--r--nihil.generator/manual_lifetime.ccm117
-rw-r--r--nihil.generator/nihil.generator.ccm32
-rw-r--r--nihil.generator/promise_base_alloc.ccm94
-rw-r--r--nihil.generator/test.cc56
-rw-r--r--nihil.generator/util.ccm37
8 files changed, 941 insertions, 0 deletions
diff --git a/nihil.generator/CMakeLists.txt b/nihil.generator/CMakeLists.txt
new file mode 100644
index 0000000..56afdac
--- /dev/null
+++ b/nihil.generator/CMakeLists.txt
@@ -0,0 +1,29 @@
+# This source code is released into the public domain.
+
+add_library(nihil.generator STATIC)
+target_sources(nihil.generator
+ PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
+ nihil.generator.ccm
+
+ generator.ccm
+ elements_of.ccm
+ manual_lifetime.ccm
+ promise_base_alloc.ccm
+ util.ccm
+)
+
+if(NIHIL_TESTS)
+ enable_testing()
+
+ add_executable(nihil.generator.test test.cc)
+ target_link_libraries(nihil.generator.test PRIVATE
+ nihil.generator
+ Catch2::Catch2WithMain
+ )
+
+ find_package(Catch2 REQUIRED)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.generator.test)
+endif()
diff --git a/nihil.generator/elements_of.ccm b/nihil.generator/elements_of.ccm
new file mode 100644
index 0000000..0e34eb9
--- /dev/null
+++ b/nihil.generator/elements_of.ccm
@@ -0,0 +1,69 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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 <concepts>
+
+export module nihil.generator:elements_of;
+
+import :util;
+
+namespace nihil {
+
+export template <typename Range, typename Allocator = use_allocator_arg>
+struct elements_of {
+ explicit constexpr elements_of(Range &&range) noexcept
+ requires std::is_default_constructible_v<Allocator>
+ : m_range(static_cast<Range &&>(range))
+ {
+ }
+
+ constexpr elements_of(Range &&range, Allocator &&alloc) noexcept
+ : m_range(static_cast<Range &&>(range))
+ , m_alloc(static_cast<Allocator &&>(alloc))
+ {}
+
+ constexpr elements_of(elements_of &&) noexcept = default;
+
+ constexpr elements_of(const elements_of &) = delete;
+
+ constexpr auto operator=(this elements_of &, const elements_of &)
+ -> elements_of & = delete;
+ constexpr auto operator=(this elements_of &, elements_of &&) noexcept
+ -> elements_of & = delete;
+
+ [[nodiscard]] constexpr auto
+ get(this elements_of const &self) noexcept -> Range &&
+ {
+ return static_cast<Range &&>(self.m_range);
+ }
+
+ [[nodiscard]] constexpr auto
+ get_allocator(this elements_of const &self) noexcept -> Allocator
+ {
+ return self.m_alloc;
+ }
+
+private:
+ [[no_unique_address]] Allocator m_alloc;
+ Range &&m_range;
+};
+
+export template <typename Range>
+elements_of(Range &&) -> elements_of<Range>;
+
+export template <typename Range, typename Allocator>
+elements_of(Range &&, Allocator &&) -> elements_of<Range, Allocator>;
+
+} // namespace nihil
diff --git a/nihil.generator/generator.ccm b/nihil.generator/generator.ccm
new file mode 100644
index 0000000..27e8103
--- /dev/null
+++ b/nihil.generator/generator.ccm
@@ -0,0 +1,507 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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 <cassert>
+#include <coroutine>
+#include <exception>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+export module nihil.generator:generator;
+
+import :elements_of;
+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
+{
+ 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;
+};
+
+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*/>;
+};
+
+// 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
+
+namespace nihil {
+
+// 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) {
+ }
+
+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()); }
+
+private:
+ __coroutine_handle __coro_;
+ bool __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:
+
+ 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 {};
+ }
+
+private:
+ 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)
+ {}
+
+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_; }
+
+private:
+ __promise_base* __promise_;
+ std::coroutine_handle<> __coro_;
+ bool __started_ = false;
+};
+
+} // namespace nihil
diff --git a/nihil.generator/manual_lifetime.ccm b/nihil.generator/manual_lifetime.ccm
new file mode 100644
index 0000000..d249e99
--- /dev/null
+++ b/nihil.generator/manual_lifetime.ccm
@@ -0,0 +1,117 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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 <concepts>
+#include <memory>
+
+export module nihil.generator:manual_lifetime;
+
+namespace nihil {
+
+template <typename T>
+struct manual_lifetime {
+ manual_lifetime() noexcept {}
+ ~manual_lifetime() {}
+
+ template <typename ...Args>
+ auto construct(this manual_lifetime &self, Args && ...args)
+ noexcept(std::is_nothrow_constructible_v<T, Args...>)
+ -> T &
+ {
+ return *::new (static_cast<void*>(std::addressof(self.m_value)))
+ T(static_cast<Args &&>(args)...);
+ }
+
+ void destruct(this manual_lifetime &self)
+ noexcept(std::is_nothrow_destructible_v<T>)
+ {
+ self.m_value.~T();
+ }
+
+ auto get(this manual_lifetime &self) noexcept -> T &
+ {
+ return self.m_value;
+ }
+
+ auto get(this manual_lifetime &&self) noexcept -> T &&
+ {
+ return static_cast<T&&>(self.m_value);
+ }
+
+ auto get(this manual_lifetime const &self) noexcept -> T const &
+ {
+ return self.m_value;
+ }
+
+ auto get(this manual_lifetime const &&self) noexcept -> T const &&
+ {
+ return static_cast<T const &&>(self.m_value);
+ }
+
+private:
+ union {
+ std::remove_const_t<T> m_value;
+ };
+};
+
+template <typename T>
+class manual_lifetime<T &> {
+ manual_lifetime() noexcept {}
+ ~manual_lifetime() {}
+
+ auto construct(this manual_lifetime &self, T &value) noexcept -> T &
+ {
+ self.m_value = std::addressof(value);
+ return self.m_value;
+ }
+
+ auto destruct(this manual_lifetime &) noexcept -> void
+ {
+ }
+
+ auto get(this manual_lifetime const &self) noexcept -> T &
+ {
+ return *self.m_value;
+ }
+
+private:
+ T *m_value = nullptr;
+};
+
+template <typename T>
+class manual_lifetime<T &&> {
+ manual_lifetime() noexcept {}
+ ~manual_lifetime() {}
+
+ auto construct(this manual_lifetime &self, T &&value) noexcept -> T &&
+ {
+ self.m_value = std::addressof(value);
+ return static_cast<T &&>(value);
+ }
+
+ void destruct(this manual_lifetime &) noexcept
+ {
+ }
+
+ auto get(this manual_lifetime const &self) noexcept -> T &&
+ {
+ return static_cast<T &&>(*self.m_value);
+ }
+
+private:
+ T* m_value = nullptr;
+};
+
+}
diff --git a/nihil.generator/nihil.generator.ccm b/nihil.generator/nihil.generator.ccm
new file mode 100644
index 0000000..9eec5b4
--- /dev/null
+++ b/nihil.generator/nihil.generator.ccm
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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 <ranges>
+
+export module nihil.generator;
+
+export import :elements_of;
+export import :generator;
+export import :manual_lifetime;
+export import :promise_base_alloc;
+export import :util;
+
+export namespace std::ranges {
+
+template <typename T, typename U, typename Alloc>
+constexpr inline bool enable_view<nihil::generator<T, U, Alloc>> = true;
+
+} // namespace std::ranges
+
diff --git a/nihil.generator/promise_base_alloc.ccm b/nihil.generator/promise_base_alloc.ccm
new file mode 100644
index 0000000..e59fc57
--- /dev/null
+++ b/nihil.generator/promise_base_alloc.ccm
@@ -0,0 +1,94 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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 <cstdlib>
+#include <memory>
+
+export module nihil.generator:promise_base_alloc;
+
+import :util;
+
+namespace nihil {
+
+template<typename Alloc>
+struct promise_base_alloc
+{
+ template<typename... Args>
+ static void* operator new(std::size_t frame_size, std::allocator_arg_t, Alloc alloc, Args &...)
+ {
+ void* frame = alloc.allocate(padded_frame_size(frame_size));
+
+ // Store allocator at end of the coroutine frame. Assuming the
+ // allocator's move constructor is non-throwing (a requirement
+ // for allocators)
+ auto *alloc_address = static_cast<void*>(std::addressof(get_allocator(frame, frame_size)));
+ ::new (alloc_address) Alloc(std::move(alloc));
+
+ return frame;
+ }
+
+ template<typename This, typename... Args>
+ static void* operator new(std::size_t frame_size, This &, std::allocator_arg_t, Alloc alloc, Args &...)
+ {
+ return promise_base_alloc::operator new(frame_size, std::allocator_arg, std::move(alloc));
+ }
+
+ static void operator delete(void* ptr, std::size_t frame_size) noexcept
+ {
+ auto &alloc = get_allocator(ptr, frame_size);
+ auto local_alloc = Alloc(std::move(alloc));
+
+ alloc.~Alloc();
+
+ local_alloc.deallocate(static_cast<std::byte*>(ptr), padded_frame_size(frame_size));
+ }
+
+private:
+ [[nodiscard]] static constexpr auto offset_of_allocator(std::size_t frame_size) noexcept -> std::size_t
+ {
+ return aligned_allocation_size(frame_size, alignof(Alloc));
+ }
+
+ [[nodiscard]] static constexpr auto padded_frame_size(std::size_t frame_size) noexcept -> std::size_t
+ {
+ return offset_of_allocator(frame_size) + sizeof(Alloc);
+ }
+
+ [[nodiscard]] static auto get_allocator(void* frame, std::size_t frame_size) noexcept -> Alloc &
+ {
+ return *reinterpret_cast<Alloc*>(
+ static_cast<char*>(frame) + offset_of_allocator(frame_size));
+ }
+
+};
+
+template<typename Alloc>
+requires (!allocator_needs_to_be_stored<Alloc>)
+struct promise_base_alloc<Alloc>
+{
+ static void* operator new(std::size_t size)
+ {
+ auto alloc = Alloc();
+ return alloc.allocate(size);
+ }
+
+ static void operator delete(void *ptr, std::size_t size) noexcept
+ {
+ auto alloc = Alloc();
+ alloc.deallocate(static_cast<std::byte *>(ptr), size);
+ }
+};
+
+} // namespace nihil
diff --git a/nihil.generator/test.cc b/nihil.generator/test.cc
new file mode 100644
index 0000000..49272b4
--- /dev/null
+++ b/nihil.generator/test.cc
@@ -0,0 +1,56 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <coroutine>
+#include <ranges>
+#include <vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.generator;
+
+TEST_CASE("generator: basic", "[generator]")
+{
+ auto fn = [] () -> nihil::generator<int> {
+ co_yield 1;
+ co_yield 2;
+ co_yield 3;
+ };
+
+ auto values = std::vector<int>();
+ std::ranges::copy(fn(), std::back_inserter(values));
+
+ REQUIRE(values == std::vector{1, 2, 3});
+}
+
+TEST_CASE("generator: exceptions", "[generator]")
+{
+ auto fn = [] () -> nihil::generator<int> {
+ co_yield 1;
+ throw std::runtime_error("test");
+ };
+
+ auto range = fn();
+ auto it = std::ranges::begin(range);
+ REQUIRE(*it == 1);
+ REQUIRE_THROWS_AS(it++, std::runtime_error);
+}
+
+TEST_CASE("generator: elements_of", "[generator]")
+{
+ auto fn1 = [] -> nihil::generator<int> {
+ co_yield 1;
+ co_yield 2;
+ co_yield 3;
+ };
+
+ auto fn2 = [&fn1] -> nihil::generator<int> {
+ co_yield nihil::elements_of(fn1());
+ };
+
+ auto values = std::vector<int>();
+ std::ranges::copy(fn2(), std::back_inserter(values));
+
+ REQUIRE(values == std::vector{1, 2, 3});
+}
diff --git a/nihil.generator/util.ccm b/nihil.generator/util.ccm
new file mode 100644
index 0000000..4d732b9
--- /dev/null
+++ b/nihil.generator/util.ccm
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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 <concepts>
+#include <memory>
+
+export module nihil.generator:util;
+
+namespace nihil {
+
+export struct use_allocator_arg {};
+
+template <typename Alloc>
+constexpr bool allocator_needs_to_be_stored =
+ !std::allocator_traits<Alloc>::is_always_equal::value ||
+ !std::is_default_constructible_v<Alloc>;
+
+// Round s up to next multiple of a.
+[[nodiscard]] constexpr auto
+aligned_allocation_size(std::size_t s, std::size_t a) -> std::size_t
+{
+ return (s + a - 1) & ~(a - 1);
+}
+
+} // namespace nihil