diff --git a/.gitignore b/.gitignore
index 259148f..6db9208 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,5 @@
*.exe
*.out
*.app
+.vscode/
+build/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..460003e
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,20 @@
+# Copyright Lewis Baker, Corentin Jabot
+# Licensed under Boost Software License 1.0
+
+cmake_minimum_required(VERSION 3.12)
+
+project(stdgenerator LANGUAGES CXX
+ VERSION 0.1)
+
+add_library(stdgenerator INTERFACE)
+
+target_include_directories(stdgenerator
+ INTERFACE
+ $)
+
+target_compile_features(stdgenerator INTERFACE cxx_std_20)
+
+enable_testing()
+include(CTest)
+
+add_subdirectory("tests")
diff --git a/include/generator b/include/generator
new file mode 100644
index 0000000..20a95f4
--- /dev/null
+++ b/include/generator
@@ -0,0 +1,802 @@
+#ifndef __STD_GENERATOR_INCLUDED
+#define __STD_GENERATOR_INCLUDED
+///////////////////////////////////////////////////////////////////////////////
+// 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)
+///////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#if __has_include()
+#include
+#else
+// Fallback for older experimental implementations of coroutines.
+#include
+namespace std {
+using std::experimental::coroutine_handle;
+using std::experimental::coroutine_traits;
+using std::experimental::noop_coroutine;
+using std::experimental::suspend_always;
+using std::experimental::suspend_never;
+} // namespace std
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if __has_include()
+# include
+#else
+
+// Placeholder implementation of the bits we need from header
+// when we don't have the header (e.g. Clang 12 and earlier).
+namespace std {
+
+// Don't create naming conflicts with recent libc++ which defines std::iter_reference_t
+// in but doesn't yet provide a header.
+template
+using __iter_reference_t = decltype(*std::declval<_T&>());
+
+template
+using iter_value_t =
+ typename std::iterator_traits>::value_type;
+
+namespace ranges {
+
+namespace __begin {
+void begin();
+
+struct _fn {
+ template
+ requires requires(_Range& __r) {
+ __r.begin();
+ }
+ auto operator()(_Range&& __r) const
+ noexcept(noexcept(__r.begin()))
+ -> decltype(__r.begin()) {
+ return __r.begin();
+ }
+
+ template
+ requires
+ (!requires(_Range& __r) { __r.begin(); }) &&
+ requires(_Range& __r) { begin(__r); }
+ auto operator()(_Range&& __r) const
+ noexcept(noexcept(begin(__r)))
+ -> decltype(begin(__r)) {
+ return begin(__r);
+ }
+};
+
+} // namespace __begin
+
+inline namespace __begin_cpo {
+inline constexpr __begin::_fn begin = {};
+}
+
+namespace __end {
+void end();
+
+struct _fn {
+ template
+ requires requires(_Range& __r) { __r.end(); }
+ auto operator()(_Range&& __r) const
+ noexcept(noexcept(__r.end()))
+ -> decltype(__r.end()) {
+ return __r.end();
+ }
+
+ template
+ requires
+ (!requires(_Range& __r) { __r.end(); }) &&
+ requires(_Range& __r) { end(__r); }
+ auto operator()(_Range&& __r) const
+ noexcept(noexcept(end(__r)))
+ -> decltype(end(__r)) {
+ return end(__r);
+ }
+};
+} // namespace __end
+
+inline namespace _end_cpo {
+inline constexpr __end::_fn end = {};
+}
+
+template
+using iterator_t = decltype(begin(std::declval<_Range>()));
+
+template
+using sentinel_t = decltype(end(std::declval<_Range>()));
+
+template
+using range_reference_t = __iter_reference_t>;
+
+template
+using range_value_t = iter_value_t>;
+
+template
+concept range = requires(_T& __t) {
+ ranges::begin(__t);
+ ranges::end(__t);
+};
+
+} // namespace ranges
+} // namespace std
+
+#endif // !__has_include()
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Begin implementation
+
+// Define NESTED_GENERATOR=0 before including this file to disable nested
+// generator support. e.g. to compare performance between nested and non-nested
+// implementations.
+#ifndef NESTED_GENERATOR
+# define NESTED_GENERATOR 1
+#endif
+
+namespace std {
+
+template
+class __manual_lifetime {
+ public:
+ __manual_lifetime() noexcept {}
+ ~__manual_lifetime() {}
+
+ template
+ _T& construct(_Args&&... __args) noexcept(std::is_nothrow_constructible_v<_T, _Args...>) {
+ return *::new (static_cast(std::addressof(__value_))) _T((_Args&&)__args...);
+ }
+
+ void destruct() noexcept(std::is_nothrow_destructible_v<_T>) {
+ __value_.~_T();
+ }
+
+ _T& get() & noexcept {
+ return __value_;
+ }
+ _T&& get() && noexcept {
+ return static_cast<_T&&>(__value_);
+ }
+ const _T& get() const & noexcept {
+ return __value_;
+ }
+ const _T&& get() const && noexcept {
+ return static_cast(__value_);
+ }
+
+ private:
+ union {
+ std::remove_const_t<_T> __value_;
+ };
+};
+
+template
+class __manual_lifetime<_T&> {
+ public:
+ __manual_lifetime() noexcept : __value_(nullptr) {}
+ ~__manual_lifetime() {}
+
+ _T& construct(_T& __value) noexcept {
+ __value_ = std::addressof(__value);
+ return __value;
+ }
+
+ void destruct() noexcept {}
+
+ _T& get() const noexcept {
+ return *__value_;
+ }
+
+ private:
+ _T* __value_;
+};
+
+template
+class __manual_lifetime<_T&&> {
+ public:
+ __manual_lifetime() noexcept : __value_(nullptr) {}
+ ~__manual_lifetime() {}
+
+ _T&& construct(_T&& __value) noexcept {
+ __value_ = std::addressof(__value);
+ return static_cast<_T&&>(__value);
+ }
+
+ void destruct() noexcept {}
+
+ _T&& get() const noexcept {
+ return static_cast<_T&&>(*__value_);
+ }
+
+ private:
+ _T* __value_;
+};
+
+namespace ranges {
+
+template
+struct elements_of {
+ explicit constexpr elements_of(_Rng&& __rng) noexcept
+ : __range_(static_cast<_Rng&&>(__rng))
+ {}
+
+ constexpr elements_of(elements_of&&) noexcept = default;
+
+ constexpr elements_of(const elements_of &) = delete;
+ constexpr elements_of &operator=(const elements_of &) = delete;
+ constexpr elements_of &operator=(elements_of &&) = delete;
+
+ constexpr _Rng&& get() && noexcept {
+ return static_cast<_Rng&&>(__range_);
+ }
+
+private:
+ _Rng &&__range_; // \expos
+};
+
+template
+elements_of(_Rng &&) -> elements_of<_Rng>;
+
+} // namespace ranges
+
+template
+static 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.
+constexpr size_t __aligned_allocation_size(size_t s, size_t a) {
+ return (s + a - 1) & ~(a - 1);
+}
+
+struct use_allocator_arg {};
+
+template ,
+ typename _Allocator = use_allocator_arg>
+class generator;
+
+template
+class __promise_base_alloc {
+ static constexpr std::size_t __offset_of_allocator(std::size_t __frameSize) noexcept {
+ return __aligned_allocation_size(__frameSize, alignof(_Alloc));
+ }
+
+ static constexpr std::size_t __padded_frame_size(std::size_t __frameSize) noexcept {
+ return __offset_of_allocator(__frameSize) + sizeof(_Alloc);
+ }
+
+ static _Alloc& __get_allocator(void* __frame, std::size_t __frameSize) noexcept {
+ return *reinterpret_cast<_Alloc*>(
+ static_cast(__frame) + __offset_of_allocator(__frameSize));
+ }
+
+public:
+ template
+ static void* operator new(std::size_t __frameSize, std::allocator_arg_t, _Alloc __alloc, _Args&...) {
+ void* __frame = __alloc.allocate(__padded_frame_size(__frameSize));
+
+ // Store allocator at end of the coroutine frame.
+ // Assuming the allocator's move constructor is non-throwing (a requirement for allocators)
+ ::new (static_cast(std::addressof(__get_allocator(__frame, __frameSize)))) _Alloc(std::move(__alloc));
+
+ return __frame;
+ }
+
+ template
+ static void* operator new(std::size_t __frameSize, _This&, std::allocator_arg_t, _Alloc __alloc, _Args&...) {
+ return __promise_base_alloc::operator new(__frameSize, std::allocator_arg, std::move(__alloc));
+ }
+
+ static void operator delete(void* __ptr, std::size_t __frameSize) noexcept {
+ _Alloc& __alloc = __get_allocator(__ptr, __frameSize);
+ _Alloc __localAlloc(std::move(__alloc));
+ __alloc.~Alloc();
+ __localAlloc.deallocate(static_cast(__ptr), __padded_frame_size(__frameSize));
+ }
+};
+
+template
+ requires (!__allocator_needs_to_be_stored<_Alloc>)
+class __promise_base_alloc<_Alloc> {
+public:
+ static void* operator new(std::size_t __size) {
+ _Alloc __alloc;
+ return __alloc.allocate(__size);
+ }
+
+ static void operator delete(void* __ptr, std::size_t __size) noexcept {
+ _Alloc __alloc;
+ __alloc.deallocate(static_cast(__ptr), __size);
+ }
+};
+
+template
+struct __generator_promise_base
+{
+ template
+ friend class generator;
+
+#if NESTED_GENERATOR
+ __generator_promise_base* __root_;
+ std::coroutine_handle<> __parentOrLeaf_;
+ std::exception_ptr *__exception_ = nullptr;
+#endif
+ __manual_lifetime<_Ref> __value_;
+
+#if NESTED_GENERATOR
+ explicit __generator_promise_base(std::coroutine_handle<> thisCoro) noexcept
+ : __root_(this)
+ , __parentOrLeaf_(thisCoro)
+ {}
+#else
+ __generator_promise_base() noexcept = default;
+#endif
+
+ std::suspend_always initial_suspend() noexcept {
+ return {};
+ }
+
+ void return_void() noexcept {}
+
+ void unhandled_exception() {
+#if NESTED_GENERATOR
+ if (__exception_ != nullptr) {
+ *__exception_ = std::current_exception();
+ }
+#endif
+ throw;
+ }
+
+#if NESTED_GENERATOR
+ // Transfers control back to the parent of a nested coroutine
+ struct __final_awaiter {
+ bool await_ready() noexcept {
+ return false;
+ }
+
+ template
+ 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
+ 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
+ struct __yield_sequence_awaiter {
+ _Gen __gen_;
+ std::exception_ptr __exception_;
+
+ __yield_sequence_awaiter(_Gen&& __g) noexcept
+ // Taking ownership of the generator ensures frame are destroyed
+ // in the reverse order of their creation
+ : __gen_((_Gen&&)__g) {
+ }
+
+ bool await_ready() noexcept {
+ return !__gen_.__get_coro();
+ }
+
+ // set the parent, root and exceptions pointer and
+ // resume the nested
+ template
+ 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;
+ __root.__parentOrLeaf_ = __gen_.__get_coro();
+ __nested.__exception_ = &__exception_;
+ // Immediately resume the nested coroutine (nested generator)
+ return __gen_.__get_coro();
+ }
+
+ void await_resume() {
+ if (__exception_) {
+ std::rethrow_exception(std::move(__exception_));
+ }
+ }
+ };
+
+ template
+ __yield_sequence_awaiter>
+ yield_value(std::ranges::elements_of> __g) noexcept {
+ return std::move(__g).get();
+ }
+
+ template
+ __yield_sequence_awaiter> yield_value(std::ranges::elements_of<_Rng> __x) {
+ // TODO: Consider propagating parent coroutine's allocator to child generator
+ // coroutine here.
+ return yield_value(std::ranges::elements_of([](_Rng&& __rng) -> generator<_Ref> {
+ auto __it = std::ranges::begin(__rng);
+ auto __itEnd = std::ranges::end(__rng);
+ while (__it != __itEnd) {
+ co_yield *__it;
+ ++__it;
+ }
+ }(std::move(__x).get())));
+ }
+
+ void resume() {
+ __parentOrLeaf_.resume();
+ }
+
+#else // !NESTED_GENERATOR
+ std::suspend_always yield_value(_Ref&& __x) noexcept(std::is_nothrow_move_constructible_v<_Ref>) {
+ __value_.construct((_Ref&&)__x);
+ return {};
+ }
+
+ template
+ 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>) {
+ __value_.construct((_T&&)__x);
+ return {};
+ }
+
+ std::suspend_always final_suspend() noexcept {
+ return {};
+ }
+#endif
+
+ // Disable use of co_await within this coroutine.
+ void await_transform() = delete;
+};
+
+template
+struct __generator_promise;
+
+template
+struct __generator_promise, _ByteAllocator> final
+ : public __generator_promise_base<_Ref>
+ , public __promise_base_alloc<_ByteAllocator> {
+ __generator_promise() noexcept
+#if NESTED_GENERATOR
+ : __generator_promise_base<_Ref>(std::coroutine_handle<__generator_promise>::from_promise(*this))
+#endif
+ {}
+
+ generator<_Ref, _Value, _Alloc> get_return_object() noexcept {
+ return generator<_Ref, _Value, _Alloc>{
+ std::coroutine_handle<__generator_promise>::from_promise(*this)
+ };
+ }
+};
+
+// Type-erased allocator with default allocator behaviour.
+template
+struct coroutine_traits, _Args...> {
+ using promise_type = __generator_promise, std::allocator>;
+};
+
+// Type-erased allocator with std::allocator_arg parameter
+template
+struct coroutine_traits, allocator_arg_t, _Alloc, _Args...> {
+private:
+ using __byte_allocator = typename std::allocator_traits>::template rebind_alloc;
+public:
+ using promise_type = __generator_promise, __byte_allocator>;
+};
+
+// Type-erased allocator with std::allocator_arg parameter (non-static member functions)
+template
+struct coroutine_traits, _This, allocator_arg_t, _Alloc, _Args...> {
+private:
+ using __byte_allocator = typename std::allocator_traits>::template rebind_alloc;
+public:
+ using promise_type = __generator_promise, __byte_allocator>;
+};
+
+// Generator with specified allocator type
+template
+struct coroutine_traits, _Args...> {
+ using __byte_allocator = typename std::allocator_traits>::template rebind_alloc;
+public:
+ using promise_type = __generator_promise, __byte_allocator>;
+};
+
+template
+class generator {
+ using __byte_allocator = typename std::allocator_traits>::template rebind_alloc;
+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
+ : __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_ || it.coro_.done();
+ }
+
+ iterator &operator++() {
+ __coro_.promise().value_.destruct();
+#if NESTED_GENERATOR
+ __coro_.promise().resume();
+#else
+ __coro_.resume();
+#endif
+ return *this;
+ }
+ void operator++(int) {
+ (void)operator++();
+ }
+
+ reference operator*() const noexcept {
+ return static_cast(__coro_.promise().value_.get());
+ }
+
+ private:
+ friend generator;
+
+ explicit iterator(__coroutine_handle __coro) noexcept
+ : __coro_(__coro) {}
+
+ __coroutine_handle __coro_;
+ };
+
+ iterator begin() {
+ if (__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.
+template
+class generator<_Ref, _Value, use_allocator_arg> {
+ using __promise_base = __generator_promise_base<_Ref>;
+public:
+
+ generator() noexcept = default;
+
+ 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) {
+ std::swap(__promise_, __other.__promise_);
+ std::swap(__coro_, __other.__coro_);
+ return *this;
+ }
+
+ ~iterator() = default;
+
+ friend bool operator==(const iterator &it, sentinel) noexcept {
+ return !it.__coro_ || it.__coro_.done();
+ }
+
+ iterator& operator++() {
+ __promise_->__value_.destruct();
+#if NESTED_GENERATOR
+ __promise_->resume();
+#else
+ __coro_.resume();
+#endif
+ return *this;
+ }
+
+ void operator++(int) {
+ (void)operator++();
+ }
+
+ reference operator*() const noexcept {
+ return static_cast(__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() {
+ if (__coro_) {
+ assert(!__started_);
+ __started_ = true;
+ __coro_.resume();
+ }
+ return iterator{__promise_, __coro_};
+ }
+
+ sentinel end() noexcept {
+ return {};
+ }
+
+private:
+ template
+ friend struct __generator_promise;
+
+ template
+ 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;
+};
+
+#if __has_include()
+namespace ranges {
+
+template
+constexpr inline bool enable_view> = true;
+
+} // namespace ranges
+#endif
+
+} // namespace std
+
+#endif // __STD_GENERATOR_INCLUDED
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..8fcfa01
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,11 @@
+# Copyright Lewis Baker, Corentin Jabot
+# Licensed under Boost Software License 1.0
+
+file(GLOB test-sources "*_test.cpp")
+foreach(file-path ${test-sources})
+ string( REPLACE ".cpp" "" file-path-without-ext ${file-path} )
+ get_filename_component(file-name ${file-path-without-ext} NAME)
+ add_executable( ${file-name} ${file-path})
+ target_link_libraries(${file-name} PUBLIC stdgenerator)
+ add_test(NAME "test-${file-name}" COMMAND ${file-name})
+endforeach()
diff --git a/tests/check.hpp b/tests/check.hpp
new file mode 100644
index 0000000..b5df8de
--- /dev/null
+++ b/tests/check.hpp
@@ -0,0 +1,34 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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)
+///////////////////////////////////////////////////////////////////////////////
+#ifndef CHECK_HPP_INCLUDED
+#define CHECK_HPP_INCLUDED
+
+#pragma once
+
+#include
+#include
+
+#define STRINGIFY2(X) #X
+#define STRINGIFY(X) STRINGIFY2(X)
+
+#define CHECK(X) \
+ do { \
+ if (X) ; else { \
+ ::std::puts("FAIL: " __FILE__ "(" STRINGIFY(__LINE__) ") '" #X "' was not true"); \
+ ::std::abort(); \
+ } \
+ } while (false)
+
+#define RUN(X) \
+ do { \
+ std::puts("-> " #X); \
+ std::fflush(stdout); \
+ X(); \
+ } while (false)
+
+#endif
diff --git a/tests/nested_test.cpp b/tests/nested_test.cpp
new file mode 100644
index 0000000..0e114b5
--- /dev/null
+++ b/tests/nested_test.cpp
@@ -0,0 +1,184 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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)
+///////////////////////////////////////////////////////////////////////////////
+#include
+#include
+#include
+
+#include "check.hpp"
+
+void test_yielding_elements_of_default_constructed_generator() {
+ bool started = false;
+ bool finished = false;
+ auto makeGen = [&]() -> std::generator {
+ started = true;
+ co_yield std::ranges::elements_of(std::generator{});
+ finished = true;
+ };
+
+ auto gen = makeGen();
+ CHECK(!started);
+ CHECK(!finished);
+ auto it = gen.begin();
+ CHECK(started);
+ CHECK(finished);
+ CHECK(it == gen.end());
+}
+
+void test_yielding_elements_of_empty_generator() {
+ bool started1 = false;
+ bool started2 = false;
+ bool finished = false;
+ auto makeGen = [&]() -> std::generator {
+ started1 = true;
+ co_yield std::ranges::elements_of([&]() -> std::generator {
+ started2 = true;
+ co_return;
+ }());
+ finished = true;
+ };
+
+ auto gen = makeGen();
+ CHECK(!started1);
+ CHECK(!started2);
+ CHECK(!finished);
+ auto it = gen.begin();
+ CHECK(started1);
+ CHECK(started2);
+ CHECK(finished);
+ CHECK(it == gen.end());
+}
+
+void test_yielding_elements_of_nested_one_level() {
+ int checkpoint = 0;
+ auto makeGen = [&]() -> std::generator {
+ checkpoint = 1;
+ co_yield 1;
+ checkpoint = 2;
+ co_yield std::ranges::elements_of([&]() -> std::generator {
+ checkpoint = 3;
+ co_yield 2;
+ checkpoint = 4;
+ }());
+ checkpoint = 5;
+ co_yield 3;
+ checkpoint = 6;
+ };
+
+ auto gen = makeGen();
+ CHECK(checkpoint == 0);
+ auto it = gen.begin();
+ CHECK(checkpoint == 1);
+ CHECK(it != gen.end());
+ CHECK(*it == 1);
+ ++it;
+ CHECK(checkpoint == 3);
+ CHECK(it != gen.end());
+ CHECK(*it == 2);
+ ++it;
+ CHECK(checkpoint == 5);
+ CHECK(it != gen.end());
+ CHECK(*it == 3);
+ ++it;
+ CHECK(checkpoint == 6);
+ CHECK(it == gen.end());
+}
+
+void test_yielding_elements_of_recursive() {
+ auto makeGen = [](auto& makeGen, int depth) -> std::generator {
+ co_yield depth;
+ if (depth > 0) {
+ co_yield std::ranges::elements_of(makeGen(makeGen, depth - 1));
+ co_yield -depth;
+ }
+ };
+
+ auto gen = makeGen(makeGen, 3);
+ auto it = gen.begin();
+ CHECK(it != gen.end());
+ CHECK(*it == 3);
+ ++it;
+ CHECK(it != gen.end());
+ CHECK(*it == 2);
+ ++it;
+ CHECK(it != gen.end());
+ CHECK(*it == 1);
+ ++it;
+ CHECK(it != gen.end());
+ CHECK(*it == 0);
+ ++it;
+ CHECK(it != gen.end());
+ CHECK(*it == -1);
+ ++it;
+ CHECK(it != gen.end());
+ CHECK(*it == -2);
+ ++it;
+ CHECK(it != gen.end());
+ CHECK(*it == -3);
+ ++it;
+ CHECK(it == gen.end());
+}
+
+void test_yielding_elements_of_generator_with_different_value_type() {
+ auto strings = [](int x) -> std::generator {
+ co_yield std::to_string(x);
+
+ // This should still perform O(1) nested suspend/resume operations even
+ // though the value_type is different.
+ // Note that there's not really a way to test this property, though.
+ co_yield std::ranges::elements_of([]() -> std::generator {
+ co_yield "foo";
+ co_yield "bar";
+ }());
+
+ co_yield std::to_string(x + 1);
+ }(42);
+
+ auto it = strings.begin();
+ CHECK(it != strings.end());
+ CHECK(*it == "42");
+ ++it;
+ CHECK(it != strings.end());
+ CHECK(*it == "foo");
+ ++it;
+ CHECK(it != strings.end());
+ CHECK(*it == "bar");
+ ++it;
+ CHECK(it != strings.end());
+ CHECK(*it == "43");
+ ++it;
+ CHECK(it == strings.end());
+}
+
+void test_yielding_elements_of_generator_with_different_reference_type() {
+ // TODO
+}
+
+void test_yielding_elements_of_generator_with_different_allocator_type() {
+ // TODO
+}
+
+void test_yielding_elements_of_vector() {
+ // TODO
+}
+
+void test_nested_generator_scopes_exit_innermost_scope_first() {
+ // TODO
+}
+
+int main() {
+ RUN(test_yielding_elements_of_default_constructed_generator);
+ RUN(test_yielding_elements_of_empty_generator);
+ RUN(test_yielding_elements_of_nested_one_level);
+ RUN(test_yielding_elements_of_recursive);
+ RUN(test_yielding_elements_of_generator_with_different_value_type);
+ RUN(test_yielding_elements_of_generator_with_different_reference_type);
+ RUN(test_yielding_elements_of_generator_with_different_allocator_type);
+ RUN(test_yielding_elements_of_vector);
+ RUN(test_nested_generator_scopes_exit_innermost_scope_first);
+ return 0;
+}
\ No newline at end of file
diff --git a/tests/simple_test.cpp b/tests/simple_test.cpp
new file mode 100644
index 0000000..3fa5836
--- /dev/null
+++ b/tests/simple_test.cpp
@@ -0,0 +1,179 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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)
+///////////////////////////////////////////////////////////////////////////////
+#include
+#include
+#include
+
+#include "check.hpp"
+
+// Check some basic properties of the type at compile-time.
+
+// A generator should be a 'range'
+static_assert(std::ranges::range>);
+
+// A generator should also be a 'view'
+static_assert(std::ranges::view>);
+
+//
+static_assert(std::is_same_v<
+ std::ranges::range_reference_t>,
+ const std::string&>);
+static_assert(std::is_same_v<
+ std::ranges::range_value_t>,
+ std::string>);
+
+void test_default_constructor() {
+ std::generator g;
+ CHECK(g.begin() == g.end());
+}
+
+void test_empty_generator() {
+ bool started = false;
+ auto makeGen = [&]() -> std::generator {
+ started = true;
+ co_return;
+ };
+ auto gen = makeGen();
+ CHECK(!started);
+ auto it = gen.begin();
+ CHECK(started);
+ CHECK(it == gen.end());
+}
+
+void test_move_constructor() {
+ auto g = []() -> std::generator { co_yield 42; }();
+ auto g2 = std::move(g);
+ auto it = g2.begin();
+ CHECK(it != g2.end());
+ CHECK(*it == 42);
+ ++it;
+ CHECK(it == g2.end());
+}
+
+void test_range_based_for_loop() {
+ std::generator g = []() -> std::generator { co_yield 42; }();
+ size_t count = 0;
+ for (decltype(auto) x : g) {
+ static_assert(std::is_same_v);
+ CHECK(x == 42);
+ ++count;
+ }
+ CHECK(count == 1);
+}
+
+void test_range_based_for_loop_2() {
+ static size_t count;
+ count = 0;
+
+ struct X {
+ X() { ++count; }
+ X(const X&) { ++count; }
+ X(X&&) { ++count; }
+ ~X() { --count; }
+ };
+
+ auto g = []() -> std::generator {
+ co_yield X{};
+ co_yield X{};
+ }();
+
+ size_t elementCount = 0;
+
+ for (decltype(auto) x : g) {
+ static_assert(std::is_same_v);
+
+ // 1. temporary in co_yield expression
+ // 2. reference value stored in promise
+ // 3. iteration variable
+ CHECK(count == 3);
+ ++elementCount;
+ }
+
+ CHECK(count == 0);
+ CHECK(elementCount == 2);
+}
+
+void test_range_based_for_loop_3() {
+ static size_t count;
+ count = 0;
+
+ struct X {
+ X() { ++count; }
+ X(const X&) { ++count; }
+ X(X&&) { ++count; }
+ ~X() { --count; }
+ };
+
+ auto g = []() -> std::generator {
+ co_yield X{};
+ co_yield X{};
+ }();
+
+ size_t elementCount = 0;
+
+ for (decltype(auto) x : g) {
+ static_assert(std::is_same_v);
+
+ // 1. temporary in co_yield expression
+ CHECK(count == 1);
+ ++elementCount;
+ }
+
+ CHECK(count == 0);
+ CHECK(elementCount == 2);
+}
+
+void test_dereference_iterator_copies_reference() {
+ static size_t ctorCount = 0;
+ static size_t dtorCount = 0;
+ struct X {
+ X() { ++ctorCount; }
+ X(const X&) { ++ctorCount; }
+ X(X&&) { ++ctorCount; }
+ ~X() { ++dtorCount; }
+ };
+
+ {
+ auto g = []() -> std::generator {
+ co_yield X{};
+ }();
+
+ CHECK(ctorCount == 0);
+ CHECK(dtorCount == 0);
+
+ auto it = g.begin();
+
+ CHECK(ctorCount > 0);
+ CHECK(dtorCount == 0);
+
+ for (int i = 0; i < 3; ++i) {
+ auto beforeCtorCount = ctorCount;
+ auto beforeDtorCount = dtorCount;
+ {
+ decltype(auto) x = *it;
+ CHECK(ctorCount == beforeCtorCount + 1);
+ CHECK(dtorCount == beforeDtorCount);
+ }
+ CHECK(ctorCount = beforeCtorCount + 1);
+ CHECK(dtorCount == beforeDtorCount + 1);
+ }
+ }
+
+ CHECK(ctorCount == dtorCount);
+}
+
+int main() {
+ RUN(test_default_constructor);
+ RUN(test_empty_generator);
+ RUN(test_move_constructor);
+ RUN(test_range_based_for_loop);
+ RUN(test_range_based_for_loop_2);
+ RUN(test_range_based_for_loop_3);
+ RUN(test_dereference_iterator_copies_reference);
+ return 0;
+}