From ebe4cb0bdeabd06a31072547af47cacaab7f78c0 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Wed, 2 Jul 2025 05:49:47 +0100 Subject: replace nihil::generator the new implementation is much simpler and PD-licensed. the only downside is it doesn't support elements_of. while here, move it to nihil.core. --- CMakeLists.txt | 1 - README | 5 +- nihil.cli/CMakeLists.txt | 1 - nihil.cli/command_tree.ccm | 4 +- nihil.config/CMakeLists.txt | 6 +- nihil.config/store.cc | 1 - nihil.config/store.ccm | 1 - nihil.core/CMakeLists.txt | 29 +++ nihil.core/error.test.cc | 1 - nihil.core/generator.ccm | 240 +++++++++++++++++++++ nihil.core/generator.test.cc | 109 ++++++++++ nihil.core/nihil.core.ccm | 1 + nihil.generator/CMakeLists.txt | 33 --- nihil.generator/byte_allocator.ccm | 24 --- nihil.generator/coroutine_traits.ccm | 66 ------ nihil.generator/elements_of.ccm | 66 ------ nihil.generator/forward.ccm | 26 --- nihil.generator/generator.ccm | 323 ----------------------------- nihil.generator/generator.test.cc | 91 -------- nihil.generator/generator_promise.ccm | 61 ------ nihil.generator/generator_promise_base.ccm | 200 ------------------ nihil.generator/manual_lifetime.ccm | 114 ---------- nihil.generator/nihil.generator.ccm | 27 --- nihil.generator/promise_base_alloc.ccm | 90 -------- nihil.generator/util.ccm | 34 --- nihil.std/nihil.std.ccm | 7 + 26 files changed, 395 insertions(+), 1166 deletions(-) create mode 100644 nihil.core/generator.ccm create mode 100644 nihil.core/generator.test.cc delete mode 100644 nihil.generator/CMakeLists.txt delete mode 100644 nihil.generator/byte_allocator.ccm delete mode 100644 nihil.generator/coroutine_traits.ccm delete mode 100644 nihil.generator/elements_of.ccm delete mode 100644 nihil.generator/forward.ccm delete mode 100644 nihil.generator/generator.ccm delete mode 100644 nihil.generator/generator.test.cc delete mode 100644 nihil.generator/generator_promise.ccm delete mode 100644 nihil.generator/generator_promise_base.ccm delete mode 100644 nihil.generator/manual_lifetime.ccm delete mode 100644 nihil.generator/nihil.generator.ccm delete mode 100644 nihil.generator/promise_base_alloc.ccm delete mode 100644 nihil.generator/util.ccm diff --git a/CMakeLists.txt b/CMakeLists.txt index af2bb8c..b51f7b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,6 @@ endif() add_subdirectory(nihil.std) add_subdirectory(nihil.cli) add_subdirectory(nihil.core) -add_subdirectory(nihil.generator) add_subdirectory(nihil.posix) if(NIHIL_UCL) diff --git a/README b/README index 42471d0..ed52cf9 100644 --- a/README +++ b/README @@ -11,9 +11,8 @@ license all of nihil is in the public domain, with the exception of: -- nihil/generator.ccm (BSL) -- nihil/monad.ccm (MIT) -- nihil/uuid.ccm (MIT) +- nihil.core/monad.ccm (MIT) +- nihil.core/uuid.ccm (MIT) requirements ------------ diff --git a/nihil.cli/CMakeLists.txt b/nihil.cli/CMakeLists.txt index e87d3d4..8f4d1ea 100644 --- a/nihil.cli/CMakeLists.txt +++ b/nihil.cli/CMakeLists.txt @@ -5,7 +5,6 @@ add_library(nihil.cli STATIC) target_link_libraries(nihil.cli PRIVATE nihil.std nihil.core - nihil.generator nihil.posix ) diff --git a/nihil.cli/command_tree.ccm b/nihil.cli/command_tree.ccm index 844b2cf..4c341b7 100644 --- a/nihil.cli/command_tree.ccm +++ b/nihil.cli/command_tree.ccm @@ -2,7 +2,6 @@ export module nihil.cli:command_tree; import nihil.std; -import nihil.generator; import nihil.core; import :command; @@ -103,7 +102,8 @@ struct command_tree_node final { for (auto &&node : self.m_children | std::views::values) { co_yield node; - co_yield elements_of(node.all_children()); + for (auto &&child : node.all_children()) + co_yield child; } } diff --git a/nihil.config/CMakeLists.txt b/nihil.config/CMakeLists.txt index 0d8ffee..95b3fdb 100644 --- a/nihil.config/CMakeLists.txt +++ b/nihil.config/CMakeLists.txt @@ -1,12 +1,14 @@ # This source code is released into the public domain. add_library(nihil.config STATIC) + target_link_libraries(nihil.config PRIVATE nihil.std - nihil.generator + nihil.core nihil.posix nihil.ucl ) + target_sources(nihil.config PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.config.ccm @@ -28,6 +30,8 @@ if(NIHIL_TESTS) ) target_link_libraries(nihil.config.test PRIVATE + nihil.std + nihil.core nihil.config Catch2::Catch2WithMain) diff --git a/nihil.config/store.cc b/nihil.config/store.cc index 08850c4..06c2035 100644 --- a/nihil.config/store.cc +++ b/nihil.config/store.cc @@ -3,7 +3,6 @@ module nihil.config; import nihil.std; import nihil.core; -import nihil.generator; namespace nihil::config { diff --git a/nihil.config/store.ccm b/nihil.config/store.ccm index 2ceb52b..45de35e 100644 --- a/nihil.config/store.ccm +++ b/nihil.config/store.ccm @@ -4,7 +4,6 @@ export module nihil.config:store; // The configuration store. There should only be one of these. import nihil.std; -import nihil.generator; import nihil.core; namespace nihil::config { diff --git a/nihil.core/CMakeLists.txt b/nihil.core/CMakeLists.txt index e9998a3..d0713ce 100644 --- a/nihil.core/CMakeLists.txt +++ b/nihil.core/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources(nihil.core error.ccm features.ccm flagset.ccm + generator.ccm guard.ccm match.ccm monad.ccm @@ -25,3 +26,31 @@ target_sources(nihil.core tabulate.ccm uuid.ccm ) + +if (NIHIL_TESTS) + add_executable(nihil.core.test + capture_stream.test.cc + ctype.test.cc + error.test.cc + flagset.test.cc + generator.test.cc + guard.test.cc + match.test.cc + monad.test.cc + next_word.test.cc + parse_size.test.cc + skipws.test.cc + tabulate.test.cc + ) + + target_link_libraries(nihil.core.test PRIVATE + nihil.std + nihil.core + Catch2::Catch2WithMain) + + include(CTest) + include(Catch) + catch_discover_tests(nihil.core.test) + + enable_testing() +endif () diff --git a/nihil.core/error.test.cc b/nihil.core/error.test.cc index f4ec1ee..7a923e4 100644 --- a/nihil.core/error.test.cc +++ b/nihil.core/error.test.cc @@ -4,7 +4,6 @@ import nihil.std; import nihil.core; -import nihil.util; namespace { inline constexpr auto *test_tags = "[nihil][nihil.error]"; diff --git a/nihil.core/generator.ccm b/nihil.core/generator.ccm new file mode 100644 index 0000000..4d2bfe3 --- /dev/null +++ b/nihil.core/generator.ccm @@ -0,0 +1,240 @@ +// generator - Single-header, ranges-compatible generator type built +// on C++20 coroutines +// Written in 2021 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at https://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +export module nihil.core:generator; + +import nihil.std; + +namespace nihil { + +export template +struct generator +{ +private: + struct promise + { + using value_type = std::remove_reference_t; + using reference_type = + std::conditional_t, value_type, value_type &>; + using pointer_type = + std::conditional_t, value_type, value_type *>; + + promise() = default; + + [[nodiscard]] auto get_return_object() -> generator + { + return generator(std::coroutine_handle::from_promise(*this)); + } + + [[nodiscard]] auto initial_suspend() const -> std::suspend_always + { + return {}; + } + + [[nodiscard]] auto final_suspend() const noexcept -> std::suspend_always + { + return {}; + } + + void return_void() const noexcept + { + } + + void unhandled_exception() noexcept + { + exception_ = std::current_exception(); + } + + void rethrow_if_exception() + { + if (exception_) { + std::rethrow_exception(exception_); + } + } + + std::suspend_always yield_value(value_type &&v) noexcept + { + value_ = std::move(v); + return {}; + } + + std::suspend_always yield_value(value_type const &v) noexcept + requires(!std::is_reference_v) + { + value_ = v; + return {}; + } + + std::suspend_always yield_value(value_type &v) noexcept + requires(std::is_reference_v) + { + value_ = &v; + return {}; + } + + std::exception_ptr exception_; + std::variant value_; + }; + +public: + using promise_type = promise; + class sentinel + { + }; + + class iterator + { + using handle_type = std::coroutine_handle; + + public: + using value_type = typename promise_type::value_type; + using reference_type = typename promise_type::reference_type; + using pointer_type = typename promise_type::pointer_type; + using difference_type = std::ptrdiff_t; + + iterator() = default; + ~iterator() + { + if (handle_) + handle_.destroy(); + } + + // Non-copyable because coroutine handles point to a unique resource + iterator(iterator const &) = delete; + iterator(iterator &&rhs) noexcept + : handle_(std::exchange(rhs.handle_, nullptr)) + { + } + + auto operator=(iterator const &) -> iterator & = delete; + + auto operator=(iterator &&rhs) noexcept -> iterator & + { + handle_ = std::exchange(rhs.handle_, nullptr); + return *this; + } + + friend auto operator==(iterator const &it, sentinel) noexcept -> bool + { + return (!it.handle_ || it.handle_.done()); + } + + auto operator++() -> iterator & + { + handle_.resume(); + if (handle_.done()) { + handle_.promise().rethrow_if_exception(); + } + return *this; + } + + void operator++(int) + { + (void)this->operator++(); + } + + reference_type operator*() const + noexcept(noexcept(std::is_nothrow_copy_constructible_v)) + requires(std::is_reference_v) + { + return *std::get(handle_.promise().value_); + } + + reference_type operator*() const + noexcept(noexcept(std::is_nothrow_copy_constructible_v)) + requires(!std::is_reference_v) + { + return std::get(handle_.promise().value_); + } + + value_type *operator->() const + noexcept(noexcept(std::is_nothrow_copy_constructible_v)) + requires(std::is_reference_v) + { + return std::get(handle_.promise().value_); + } + + value_type *operator->() const + noexcept(noexcept(std::is_nothrow_copy_constructible_v)) + requires(!std::is_reference_v) + { + return &std::get(handle_.promise().value_); + } + + private: + friend struct generator; + + explicit iterator(handle_type handle) + : handle_(handle) + { + } + + handle_type handle_; + }; + + using handle_type = std::coroutine_handle; + + generator() noexcept = default; + ~generator() + { + if (handle_) + handle_.destroy(); + } + + generator(generator const &) = delete; + + generator(generator &&rhs) noexcept + : handle_(std::exchange(rhs.handle_, nullptr)) + { + } + + auto operator=(generator const &) -> generator & = delete; + + auto operator=(generator &&rhs) noexcept -> generator & + { + swap(rhs); + return *this; + } + + auto begin() -> iterator + { + handle_.resume(); + if (handle_.done()) { + handle_.promise().rethrow_if_exception(); + } + return iterator(std::exchange(handle_, nullptr)); + } + + auto end() const noexcept -> sentinel + { + return {}; + } + + void swap(generator &other) noexcept + { + std::swap(handle_, other.handle_); + } + +private: + friend class iterator; + explicit generator(handle_type handle) noexcept + : handle_(handle) + { + } + + handle_type handle_ = nullptr; +}; +} // namespace nihil + +export template +inline constexpr bool std::ranges::enable_view> = true; diff --git a/nihil.core/generator.test.cc b/nihil.core/generator.test.cc new file mode 100644 index 0000000..1fc0f22 --- /dev/null +++ b/nihil.core/generator.test.cc @@ -0,0 +1,109 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { +inline auto constexpr test_tags = "[nihil][nihil.generator]"; + +SCENARIO("A generator that yields values", test_tags) +{ + GIVEN ("A generator that yields values") { + auto fn = [&]() -> nihil::generator { + co_yield 1; + co_yield 2; + }; + + THEN ("The generator yields the original values") { + REQUIRE(std::ranges::equal(fn(), std::vector{1, 2})); + } + } +} + +SCENARIO("A generator that yields references", test_tags) +{ + GIVEN ("A generator that yields references") { + auto one = 1, two = 2; + auto fn = [&]() -> nihil::generator { + co_yield one; + co_yield two; + }; + auto range = fn(); + + THEN ("The references refer to the original values") { + auto it = std::ranges::begin(range); + REQUIRE(&*it == &one); + ++it; + REQUIRE(&*it == &two); + ++it; + REQUIRE(it == std::ranges::end(range)); + } + } +} + +SCENARIO("A generator that yields pointers", test_tags) +{ + GIVEN ("A generator that yields pointers") { + auto one = 1, two = 2; + auto fn = [&]() -> nihil::generator { + co_yield &one; + co_yield &two; + }; + + THEN ("The pointers point to the original values") { + REQUIRE(std::ranges::equal(fn(), std::vector{&one, &two})); + } + } +} + +SCENARIO("A generator that yields lvalues", test_tags) +{ + GIVEN ("A generator that yields pointers") { + auto one = 1, two = 2; + auto fn = [&]() -> nihil::generator { + co_yield one; + co_yield two; + }; + + THEN ("The pointers point to the original values") { + REQUIRE(std::ranges::equal(fn(), std::vector{1, 2})); + } + } +} + +TEST_CASE("generator: exceptions", "[generator]") +{ + auto fn = []() -> nihil::generator { + 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); +} + +#if 0 +// TODO: Re-enable this test once we have a standard-compliant generator. +TEST_CASE("generator: elements_of", "[generator]") +{ + auto fn1 = [] -> nihil::generator { + co_yield 1; + co_yield 2; + co_yield 3; + }; + + auto fn2 = [&fn1] -> nihil::generator { + co_yield nihil::elements_of(fn1()); + }; + + auto values = std::vector(); + std::ranges::copy(fn2(), std::back_inserter(values)); + + REQUIRE(values == std::vector{1, 2, 3}); +} +#endif +} // anonymous namespace diff --git a/nihil.core/nihil.core.ccm b/nihil.core/nihil.core.ccm index 37ad032..7634026 100644 --- a/nihil.core/nihil.core.ccm +++ b/nihil.core/nihil.core.ccm @@ -8,6 +8,7 @@ export import :errc; export import :error; export import :features; export import :flagset; +export import :generator; export import :guard; export import :match; export import :monad; diff --git a/nihil.generator/CMakeLists.txt b/nihil.generator/CMakeLists.txt deleted file mode 100644 index d9eb854..0000000 --- a/nihil.generator/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# This source code is released into the public domain. - -add_library(nihil.generator STATIC) -target_link_libraries(nihil.generator PRIVATE nihil.std) -target_sources(nihil.generator - PUBLIC FILE_SET modules TYPE CXX_MODULES FILES - 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 -) - -if(NIHIL_TESTS) - enable_testing() - - add_executable(nihil.generator.test generator.test.cc) - target_link_libraries(nihil.generator.test PRIVATE - nihil.generator - Catch2::Catch2WithMain - ) - - include(CTest) - include(Catch) - catch_discover_tests(nihil.generator.test) -endif() diff --git a/nihil.generator/byte_allocator.ccm b/nihil.generator/byte_allocator.ccm deleted file mode 100644 index 86b2edf..0000000 --- a/nihil.generator/byte_allocator.ccm +++ /dev/null @@ -1,24 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:byte_allocator; - -import nihil.std; - -namespace nihil { - -template -using byte_allocator_t = typename std::allocator_traits< - std::remove_cvref_t>::template rebind_alloc; - -} // namespace nihil diff --git a/nihil.generator/coroutine_traits.ccm b/nihil.generator/coroutine_traits.ccm deleted file mode 100644 index fde4393..0000000 --- a/nihil.generator/coroutine_traits.ccm +++ /dev/null @@ -1,66 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:coroutine_traits; - -import nihil.std; -import :byte_allocator; -import :forward; -import :generator_promise; - -namespace std { - -// Type-erased allocator with default allocator behaviour. -export template -struct coroutine_traits, Args...> -{ - using promise_type = - nihil::generator_promise, std::allocator>; -}; - -// Type-erased allocator with std::allocator_arg parameter -export template -struct coroutine_traits, allocator_arg_t, Alloc, Args...> -{ -private: - using byte_allocator = nihil::byte_allocator_t; - -public: - using promise_type = nihil::generator_promise, byte_allocator, - true /*explicit Allocator*/>; -}; - -// Type-erased allocator with std::allocator_arg parameter (non-static member functions) -export template -struct coroutine_traits, This, allocator_arg_t, Alloc, Args...> -{ -private: - using byte_allocator = nihil::byte_allocator_t; - -public: - using promise_type = nihil::generator_promise, byte_allocator, - true /*explicit Allocator*/>; -}; - -// Generator with specified allocator type -export template -struct coroutine_traits, Args...> -{ - using byte_allocator = nihil::byte_allocator_t; - -public: - using promise_type = - nihil::generator_promise, byte_allocator>; -}; - -} // namespace std diff --git a/nihil.generator/elements_of.ccm b/nihil.generator/elements_of.ccm deleted file mode 100644 index 74c8b76..0000000 --- a/nihil.generator/elements_of.ccm +++ /dev/null @@ -1,66 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:elements_of; - -import nihil.std; -import :util; - -namespace nihil { - -export template -struct elements_of { - explicit constexpr elements_of(Range &&range) noexcept - requires std::is_default_constructible_v - : m_range(static_cast(range)) - { - } - - constexpr elements_of(Range &&range, Allocator &&alloc) noexcept - : m_range(static_cast(range)) - , m_alloc(static_cast(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(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 -elements_of(Range &&) -> elements_of; - -export template -elements_of(Range &&, Allocator &&) -> elements_of; - -} // namespace nihil diff --git a/nihil.generator/forward.ccm b/nihil.generator/forward.ccm deleted file mode 100644 index 2aa2448..0000000 --- a/nihil.generator/forward.ccm +++ /dev/null @@ -1,26 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:forward; - -import nihil.std; -import :util; - -namespace nihil { - -export template , - typename Allocator = use_allocator_arg> -struct generator; - -} // namespace nihil diff --git a/nihil.generator/generator.ccm b/nihil.generator/generator.ccm deleted file mode 100644 index a2fcafb..0000000 --- a/nihil.generator/generator.ccm +++ /dev/null @@ -1,323 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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 - -export module nihil.generator:generator; - -import nihil.std; -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 { - -// TODO : make layout compatible promise casts possible -export template -struct generator -{ - using byte_allocator = byte_allocator_t; - -public: - using promise_type = generator_promise, byte_allocator>; - friend promise_type; - -private: - using coroutine_handle = std::coroutine_handle; - -public: - 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; - - 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(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 {}; - } - -private: - explicit generator(coroutine_handle coro) noexcept - : m_coro(coro) - { - } - -public: // to get around access restrictions for __yield_sequence_awaitable - 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 m_coro; - bool m_started = false; -}; - -// Specialisation for type-erased allocator implementation. -export template -class generator -{ - using promise_base = generator_promise_base; - -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; - - 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(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 - friend struct generator_promise; - - template - explicit generator(std::coroutine_handle coro) noexcept - : m_promise(std::addressof(coro.promise())) - , m_coro(coro) - { - } - -public: // to get around access restrictions for __yield_sequence_awaitable - auto get_coro() noexcept -> std::coroutine_handle<> - { - return m_coro; - } - - auto get_promise() noexcept -> promise_base * - { - return m_promise; - } - -private: - promise_base *m_promise; - std::coroutine_handle<> m_coro; - bool m_started = false; -}; - -} // namespace nihil diff --git a/nihil.generator/generator.test.cc b/nihil.generator/generator.test.cc deleted file mode 100644 index 1782dfb..0000000 --- a/nihil.generator/generator.test.cc +++ /dev/null @@ -1,91 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.generator; - -namespace { -inline auto constexpr test_tags = "[nihil][nihil.generator]"; - -SCENARIO("A generator that yields values", test_tags) -{ - GIVEN ("A generator that yields values") { - auto fn = [&]() -> nihil::generator { - co_yield 1; - co_yield 2; - }; - - THEN ("The generator yields the original values") { - REQUIRE(std::ranges::equal(fn(), std::vector{1, 2})); - } - } -} - -SCENARIO("A generator that yields references", test_tags) -{ - GIVEN ("A generator that yields references") { - auto one = 1, two = 2; - auto fn = [&]() -> nihil::generator { - co_yield one; - co_yield two; - }; - auto range = fn(); - - THEN ("The references refer to the original values") { - auto it = std::ranges::begin(range); - REQUIRE(&*it == &one); - ++it; - REQUIRE(&*it == &two); - ++it; - REQUIRE(it == std::ranges::end(range)); - } - } -} - -SCENARIO("A generator that yields pointers", test_tags) -{ - GIVEN ("A generator that yields pointers") { - auto one = 1, two = 2; - auto fn = [&]() -> nihil::generator { - co_yield &one; - co_yield &two; - }; - - THEN ("The pointers point to the original values") { - REQUIRE(std::ranges::equal(fn(), std::vector{&one, &two})); - } - } -} - -TEST_CASE("generator: exceptions", "[generator]") -{ - auto fn = []() -> nihil::generator { - 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 { - co_yield 1; - co_yield 2; - co_yield 3; - }; - - auto fn2 = [&fn1] -> nihil::generator { - co_yield nihil::elements_of(fn1()); - }; - - auto values = std::vector(); - std::ranges::copy(fn2(), std::back_inserter(values)); - - REQUIRE(values == std::vector{1, 2, 3}); -} -} // anonymous namespace diff --git a/nihil.generator/generator_promise.ccm b/nihil.generator/generator_promise.ccm deleted file mode 100644 index 3e8aa8c..0000000 --- a/nihil.generator/generator_promise.ccm +++ /dev/null @@ -1,61 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:generator_promise; - -import nihil.std; -import :forward; -import :generator_promise_base; -import :promise_base_alloc; - -namespace nihil { - -export template -struct generator_promise; - -export template -struct generator_promise, ByteAllocator, ExplicitAllocator> final - : public generator_promise_base, - public promise_base_alloc -{ - generator_promise() noexcept - : generator_promise_base( - std::coroutine_handle::from_promise(*this)) - { - } - - auto get_return_object() noexcept -> generator - { - return generator{ - std::coroutine_handle::from_promise(*this)}; - } - - using generator_promise_base::yield_value; - - template - auto yield_value(nihil::elements_of &&x) -> typename generator_promise_base< - Ref>::template yield_sequence_awaiter> - { - 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 { - for (auto &&e : rng) - co_yield static_cast(e); - }(std::forward(x.get())); - } -}; - -} // namespace nihil diff --git a/nihil.generator/generator_promise_base.ccm b/nihil.generator/generator_promise_base.ccm deleted file mode 100644 index 6b11b40..0000000 --- a/nihil.generator/generator_promise_base.ccm +++ /dev/null @@ -1,200 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:generator_promise_base; - -import nihil.std; -import :elements_of; -import :forward; -import :manual_lifetime; - -namespace nihil { - -template -struct generator_promise_base -{ - template - friend struct 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 m_exception; - manual_lifetime 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 - auto - await_suspend(std::coroutine_handle 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) - -> std::suspend_always - { - m_root->m_value.construct((Ref &&)x); - return {}; - } - - template - requires(!std::is_reference_v) && std::is_convertible_v - auto - yield_value(T &&x) noexcept(std::is_nothrow_constructible_v) -> std::suspend_always - { - m_root->m_value.construct((T &&)x); - return {}; - } - - template - 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 - auto - await_suspend(std::coroutine_handle 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 - auto yield_value(nihil::elements_of> g) noexcept - -> yield_sequence_awaiter> - - { - return std::move(g).get(); - } - - template - auto yield_value(nihil::elements_of &&x) - -> yield_sequence_awaiter, Allocator>> - - { - return [](std::allocator_arg_t, Allocator, - auto &&rng) -> generator, Allocator> { - for (auto &&e : rng) - co_yield static_cast(e); - }(std::allocator_arg, x.get_allocator(), std::forward(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 deleted file mode 100644 index 44bc0a8..0000000 --- a/nihil.generator/manual_lifetime.ccm +++ /dev/null @@ -1,114 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:manual_lifetime; - -import nihil.std; - -namespace nihil { - -template -struct manual_lifetime { - manual_lifetime() noexcept {} - ~manual_lifetime() {} - - template - auto construct(this manual_lifetime &self, Args && ...args) - noexcept(std::is_nothrow_constructible_v) - -> T & - { - return *::new (static_cast(std::addressof(self.m_value))) - T(static_cast(args)...); - } - - void destruct(this manual_lifetime &self) - noexcept(std::is_nothrow_destructible_v) - { - 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(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(self.m_value); - } - -private: - union { - std::remove_const_t m_value; - }; -}; - -template -struct manual_lifetime { - manual_lifetime() noexcept = default; - ~manual_lifetime() = default; - - auto construct(this manual_lifetime &self, T &value) noexcept -> T & - { - self.m_value = std::addressof(value); - return 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 -struct manual_lifetime { - manual_lifetime() noexcept = default; - ~manual_lifetime() = default; - - auto construct(this manual_lifetime &self, T &&value) noexcept -> T && - { - self.m_value = std::addressof(value); - return static_cast(value); - } - - void destruct(this manual_lifetime &) noexcept - { - } - - auto get(this manual_lifetime const &self) noexcept -> T && - { - return static_cast(*self.m_value); - } - -private: - T* m_value = nullptr; -}; - -} diff --git a/nihil.generator/nihil.generator.ccm b/nihil.generator/nihil.generator.ccm deleted file mode 100644 index fc6a097..0000000 --- a/nihil.generator/nihil.generator.ccm +++ /dev/null @@ -1,27 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator; - -import nihil.std; - -export import :coroutine_traits; -export import :elements_of; -export import :generator; - -export namespace std::ranges { // NOLINT - -template -constexpr inline bool enable_view> = true; - -} // namespace std::ranges diff --git a/nihil.generator/promise_base_alloc.ccm b/nihil.generator/promise_base_alloc.ccm deleted file mode 100644 index 7fd544b..0000000 --- a/nihil.generator/promise_base_alloc.ccm +++ /dev/null @@ -1,90 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:promise_base_alloc; - -import nihil.std; -import :util; - -namespace nihil { - -template -struct promise_base_alloc -{ - template - 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(std::addressof(get_allocator(frame, frame_size))); - ::new (alloc_address) Alloc(std::move(alloc)); - - return frame; - } - - template - 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(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( - static_cast(frame) + offset_of_allocator(frame_size)); - } - -}; - -template -requires (!allocator_needs_to_be_stored) -struct promise_base_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(ptr), size); - } -}; - -} // namespace nihil diff --git a/nihil.generator/util.ccm b/nihil.generator/util.ccm deleted file mode 100644 index 259499a..0000000 --- a/nihil.generator/util.ccm +++ /dev/null @@ -1,34 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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) -/////////////////////////////////////////////////////////////////////////////// - -export module nihil.generator:util; - -import nihil.std; - -namespace nihil { - -export struct use_allocator_arg {}; - -template -constexpr bool allocator_needs_to_be_stored = - !std::allocator_traits::is_always_equal::value || - !std::is_default_constructible_v; - -// 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 diff --git a/nihil.std/nihil.std.ccm b/nihil.std/nihil.std.ccm index 349ab95..cb6a46d 100644 --- a/nihil.std/nihil.std.ccm +++ b/nihil.std/nihil.std.ccm @@ -421,6 +421,7 @@ using std::system_error; // using std::add_pointer_t; +using std::conditional_t; using std::false_type; using std::invoke_result; using std::is_convertible; @@ -437,10 +438,14 @@ using std::is_move_assignable; using std::is_move_assignable_v; using std::is_nothrow_constructible; using std::is_nothrow_constructible_v; +using std::is_nothrow_copy_constructible; +using std::is_nothrow_copy_constructible_v; using std::is_nothrow_destructible; using std::is_nothrow_destructible_v; using std::is_nothrow_move_constructible; using std::is_nothrow_move_constructible_v; +using std::is_pointer; +using std::is_pointer_v; using std::is_reference; using std::is_reference_v; using std::is_same; @@ -448,6 +453,7 @@ using std::is_same_v; using std::remove_const_t; using std::remove_cv_t; using std::remove_cvref_t; +using std::remove_reference_t; using std::true_type; // @@ -463,6 +469,7 @@ using std::move; using std::pair; // +using std::get; using std::get_if; using std::monostate; using std::variant; -- cgit v1.2.3