[libcxx-commits] [libcxx] [libc++] P2502R2: `std::generator` (PR #92213)

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jul 17 10:59:14 PDT 2024


================
@@ -0,0 +1,593 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP_GENERATOR
+#define _LIBCPP_GENERATOR
+
+/*
+
+namespace std {
+  // [coro.generator.class], class template generator
+  template<class Ref, class V = void, class Allocator = void>
+    class generator;
+
+  namespace pmr {
+    template<class R, class V = void>
+      using generator = std::generator<R, V, polymorphic_allocator<>>;
+  }
+}
+
+*/
+
+#include <__algorithm/max.h>
+#include <__assert>
+#include <__concepts/common_reference_with.h>
+#include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
+#include <__concepts/same_as.h>
+#include <__config>
+#include <__coroutine/coroutine_handle.h>
+#include <__coroutine/coroutine_traits.h>
+#include <__coroutine/noop_coroutine_handle.h>
+#include <__coroutine/trivial_awaitables.h>
+#include <__exception/exception_ptr.h>
+#include <__iterator/default_sentinel.h>
+#include <__memory/addressof.h>
+#include <__memory/allocator_arg_t.h>
+#include <__memory/allocator_traits.h>
+#include <__memory/construct_at.h>
+#include <__memory_resource/polymorphic_allocator.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/elements_of.h>
+#include <__ranges/view_interface.h>
+#include <__type_traits/add_pointer.h>
+#include <__type_traits/common_reference.h>
+#include <__type_traits/conditional.h>
+#include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/is_object.h>
+#include <__type_traits/is_pointer.h>
+#include <__type_traits/is_reference.h>
+#include <__type_traits/is_void.h>
+#include <__type_traits/remove_cvref.h>
+#include <__type_traits/remove_reference.h>
+#include <__utility/exchange.h>
+#include <__utility/move.h>
+#include <__utility/swap.h>
+#include <cstdint>
+#include <new>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+#if _LIBCPP_STD_VER >= 23
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Ref, class _Val>
+using __gen_val = conditional_t<is_void_v<_Val>, remove_cvref_t<_Ref>, _Val>;
+
+template <class _Ref, class _Val>
+using __gen_ref = conditional_t<is_void_v<_Val>, _Ref&&, _Ref>;
+
+template <class _Ref, class _Val>
+using __gen_yielded =
+    conditional_t<is_reference_v<__gen_ref<_Ref, _Val>>, __gen_ref<_Ref, _Val>, const __gen_ref<_Ref, _Val>&>;
+
+template <class, class, class>
+class generator;
+
+template <class _Yielded>
+class __gen_promise_base {
+private:
+  template <class, class, class>
+  friend class generator;
+
+  template <class, class>
+  friend class __gen_iter;
+
+  // Each promise object stores either a `__root_data` when associated with a root generator, or a `__recursive_data`
+  // when associated with a generator that is yielded recursively.
+  struct __root_data {
+    // The client code has access only to the iterator of the root generator. Thus, the root generator must store the
+    // yielded values of recursively-yielded generators, which will then be returned when the client code dereferences
+    // the iterator.
+    add_pointer_t<_Yielded> __value_ptr;
+    // The client code has access only to the iterator of the root generator. Thus, the root generator needs to identify
+    // which generator is currently active. This active generator will then be resumed when the client code increments
+    // the iterator.
+    std::coroutine_handle<__gen_promise_base> __active;
+  };
+
+  struct __recursive_data {
+    std::exception_ptr __exception;
+    std::coroutine_handle<__gen_promise_base> __parent;
+    std::coroutine_handle<__gen_promise_base> __root;
+  };
+
+  union __union {
+    __root_data __root;
+    __recursive_data __recursive;
+
+    _LIBCPP_HIDE_FROM_ABI ~__union() noexcept {}
+  } __data_;
+
+  // The `__tag_` stores the active member of the `__data_` union:
+  // - `false` indicates that the active member is `__root`.
+  // - `true` indicates that the active member is `__recursive`.
+  // This field can be omitted because `__recursive_data.__root` can store the active member.
+  bool __tag_;
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool __is_root() noexcept { return !__tag_; }
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI __root_data& __get_root_data() noexcept {
+    _LIBCPP_ASSERT_INTERNAL(__is_root(), "the active member of `__data_` is not `__root`");
+    return __data_.__root;
+  }
+  _LIBCPP_HIDE_FROM_ABI void __set_root_tag() noexcept { __tag_ = false; }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool __is_recursive() noexcept { return __tag_; }
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI __recursive_data& __get_recursive_data() noexcept {
+    _LIBCPP_ASSERT_INTERNAL(__is_recursive(), "the active member of `__data_` is not `__recursive`");
+    return __data_.__recursive;
+  }
+  _LIBCPP_HIDE_FROM_ABI void __set_recursive_tag() noexcept { __tag_ = true; }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::coroutine_handle<__gen_promise_base>& __active() noexcept {
+    _LIBCPP_ASSERT_INTERNAL(__is_root(), "the active member of `__data_` is not `__root`");
+    return __get_root_data().__active;
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI add_pointer_t<_Yielded>& __value_ptr() noexcept {
+    _LIBCPP_ASSERT_INTERNAL(__is_root(), "the active member of `__data_` is not `__root`");
+    return __get_root_data().__value_ptr;
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI add_pointer_t<_Yielded>& __root_value_ptr() noexcept {
+    if (__is_root()) {
+      return __value_ptr();
+    }
+    return __get_recursive_data().__root.promise().__value_ptr();
+  }
+
+  struct __element_awaiter {
+    [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool await_ready() noexcept { return false; }
+
+    template <class _Promise>
+    _LIBCPP_HIDE_FROM_ABI void await_suspend(coroutine_handle<_Promise> __current) noexcept {
+      __current.promise().__root_value_ptr() = std::addressof(__value);
+    }
+
+    _LIBCPP_HIDE_FROM_ABI void await_resume() noexcept {}
+
+    remove_cvref_t<_Yielded> __value;
+  };
+
+  struct __final_awaiter {
+    [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool await_ready() noexcept { return false; }
+
+    template <class _Promise>
+    _LIBCPP_HIDE_FROM_ABI coroutine_handle<> await_suspend(coroutine_handle<_Promise> __current) noexcept {
+      // Checks if the current generator is recursively-yielded
+      if (__current.promise().__is_recursive()) {
+        auto&& __recursive_data = __current.promise().__get_recursive_data();
+        auto __parent           = __recursive_data.__parent;
+        // Updates the active generator to its parent, allowing the client code to resume it later
+        __recursive_data.__root.promise().__active() = __parent;
+        // Transfers execution to its parent, which is the generator that yields it
+        return __parent;
+      }
+      return std::noop_coroutine();
+    }
+
+    _LIBCPP_HIDE_FROM_ABI void await_resume() noexcept {}
+  };
+
+  template <class _Ref, class _Val, class _Allocator>
+  struct __recursive_awaiter {
+    _LIBCPP_HIDE_FROM_ABI explicit __recursive_awaiter(generator<_Ref, _Val, _Allocator>&& __gen) noexcept
+        : __gen_{std::move(__gen)} {}
+
+    [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool await_ready() noexcept { return !__gen_.__coroutine_; }
+
+    template <class _Promise>
+    _LIBCPP_HIDE_FROM_ABI coroutine_handle<__gen_promise_base>
+    await_suspend(coroutine_handle<_Promise> __current) noexcept {
+      // Stores a `__recursive_data` in the promise object associated with `__gen`
+      auto __recursive = coroutine_handle<__gen_promise_base>::from_address(__gen_.__coroutine_.address());
+      __recursive.promise().__set_recursive_tag();
+      auto&& __recursive_data = __recursive.promise().__get_recursive_data();
+
+      // Sets `__recursive_data.__parent` to the current generator
+      auto __parent             = coroutine_handle<__gen_promise_base>::from_address(__current.address());
+      __recursive_data.__parent = __parent;
+
+      // Sets `__recursive_data.__root` to the current generator if it's a root generator, or to the root generator of
+      // the current generator otherwise
+      if (__parent.promise().__is_recursive()) {
+        __recursive_data.__root = __parent.promise().__get_recursive_data().__root;
+      } else {
+        __recursive_data.__root = __parent;
+      }
+
+      // Updates the active generator to `__gen`, allowing the client code to resume it later
+      __recursive_data.__root.promise().__active() = __recursive;
+
+      // Transfers execution to `__gen`
+      return __recursive;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI void await_resume() {
+      auto __recursive        = coroutine_handle<__gen_promise_base>::from_address(__gen_.__coroutine_.address());
+      auto&& __recursive_data = __recursive.promise().__get_recursive_data();
+      if (__recursive_data.__exception) {
+        std::rethrow_exception(std::move(__recursive_data.__exception));
+      }
+    }
+
+  private:
+    generator<_Ref, _Val, _Allocator> __gen_;
+  };
+
+public:
+  _LIBCPP_HIDE_FROM_ABI __gen_promise_base() noexcept
+      : __data_{.__root{
+            .__value_ptr{nullptr},
+            .__active{coroutine_handle<__gen_promise_base>::from_promise(*this)},
+        }},
+        __tag_{false} {}
+
+  _LIBCPP_HIDE_FROM_ABI ~__gen_promise_base() noexcept {
+    if (__is_root()) {
+      __data_.__root.~__root_data();
+    } else {
+      __data_.__recursive.~__recursive_data();
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI suspend_always initial_suspend() const noexcept { return {}; }
+
+  _LIBCPP_HIDE_FROM_ABI __final_awaiter final_suspend() noexcept { return {}; }
+
+  _LIBCPP_HIDE_FROM_ABI suspend_always yield_value(_Yielded __value) noexcept {
+    __root_value_ptr() = std::addressof(__value);
+    return {};
+  }
+
+  _LIBCPP_HIDE_FROM_ABI auto yield_value(const remove_reference_t<_Yielded>& __value) noexcept(
+      is_nothrow_constructible_v<remove_cvref_t<_Yielded>, const remove_reference_t<_Yielded>&>)
+    requires is_rvalue_reference_v<_Yielded> &&
+             constructible_from<remove_cvref_t<_Yielded>, const remove_reference_t<_Yielded>&>
+  {
+    return __element_awaiter{.__value{__value}};
+  }
+
+  template <class _Ref2, class _Val2, class _Allocator2, class _Unused>
+    requires same_as<__gen_yielded<_Ref2, _Val2>, _Yielded>
+  _LIBCPP_HIDE_FROM_ABI auto
+  yield_value(ranges::elements_of<generator<_Ref2, _Val2, _Allocator2>&&, _Unused> __elements) noexcept {
+    return __recursive_awaiter<_Ref2, _Val2, _Allocator2>{std::move(__elements.range)};
+  }
+
+  template <ranges::input_range _Range, class _Allocator>
+    requires convertible_to<ranges::range_reference_t<_Range>, _Yielded>
+  _LIBCPP_HIDE_FROM_ABI auto yield_value(ranges::elements_of<_Range, _Allocator> __range) {
+    auto __lambda =
+        [](allocator_arg_t, _Allocator, ranges::iterator_t<_Range> __i, ranges::sentinel_t<_Range> __s) static
+        -> generator<_Yielded, ranges::range_value_t<_Range>, _Allocator> {
+      for (; __i != __s; ++__i) {
+        co_yield static_cast<_Yielded>(*__i);
+      }
+    };
+    return yield_value(ranges::elements_of(
+        __lambda(allocator_arg, __range.allocator, ranges::begin(__range.range), ranges::end(__range.range))));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void await_transform() = delete;
+
+  _LIBCPP_HIDE_FROM_ABI void return_void() const noexcept {}
+
+  _LIBCPP_HIDE_FROM_ABI void unhandled_exception() {
+#  ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+    if (__is_root()) {
+      throw;
+    } else {
+      __get_recursive_data().__exception = std::current_exception();
+    }
+#  endif
+  }
+};
+
+struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) __gen_aligned_block {
+  char __data[__STDCPP_DEFAULT_NEW_ALIGNMENT__];
+
+  _LIBCPP_HIDE_FROM_ABI static size_t __count(const size_t __size) noexcept {
+    return (__size + sizeof(__gen_aligned_block) - 1) / sizeof(__gen_aligned_block);
+  };
+};
+
+template <class _Allocator>
+concept __gen_stateless_allocator =
+    default_initializable<_Allocator> && allocator_traits<_Allocator>::is_always_equal::value;
+
+template <class _Allocator>
+class __gen_promise_allocator {
+private:
+  using __rebind           = allocator_traits<_Allocator>::template rebind_alloc<__gen_aligned_block>;
+  using __rebind_size_type = allocator_traits<__rebind>::size_type;
+
+  using __rebind_pointer = allocator_traits<__rebind>::pointer;
+  static_assert(is_pointer_v<__rebind_pointer>);
+
+  _LIBCPP_HIDE_FROM_ABI static void* __alloc(const _Allocator& __al, size_t __size) {
+    __rebind __rebind_al = static_cast<__rebind>(__al);
+
+    if constexpr (__gen_stateless_allocator<__rebind>) {
+      const size_t __block_count = __gen_aligned_block::__count(__size);
+      return __rebind_al.allocate(static_cast<__rebind_size_type>(__block_count));
+    } else {
+      // Allocates enough blocks for the coroutine frame, the size of the stateful allocator, and the alignment of the
+      // stateful allocator
+      const size_t __block_count = __gen_aligned_block::__count(__size + sizeof(__rebind) + alignof(__rebind));
+      void* const __ptr          = __rebind_al.allocate(static_cast<__rebind_size_type>(__block_count));
+
+      // Stores the stateful allocator in the allocated block
+      __rebind* const __rebind_al_ptr = reinterpret_cast<__rebind*>(
+          (reinterpret_cast<uintptr_t>(__ptr) + __size + alignof(__rebind) - 1) & ~(alignof(__rebind) - 1));
+      std::construct_at(__rebind_al_ptr, std::move(__rebind_al));
+      return __ptr;
+    }
+  }
+
+public:
+  _LIBCPP_HIDE_FROM_ABI void* operator new(size_t __size)
+    requires default_initializable<_Allocator>
+  {
+    return __alloc({}, __size);
+  }
+
+  template <class _Allocator2, class... _Args>
+    requires convertible_to<const _Allocator2&, _Allocator>
+  _LIBCPP_HIDE_FROM_ABI void* operator new(size_t __size, allocator_arg_t, const _Allocator2& __al, const _Args&...) {
+    return __alloc(static_cast<_Allocator>(__al), __size);
+  }
+
+  template <class _This, class _Allocator2, class... _Args>
+    requires convertible_to<const _Allocator2&, _Allocator>
+  _LIBCPP_HIDE_FROM_ABI void*
+  operator new(size_t __size, const _This&, allocator_arg_t, const _Allocator2& __al, const _Args&...) {
+    return __alloc(static_cast<_Allocator>(__al), __size);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI static void operator delete(void* const __ptr, size_t __size) noexcept {
+    if constexpr (__gen_stateless_allocator<__rebind>) {
+      __rebind __rebind_al;
+      const size_t __block_count = __gen_aligned_block::__count(__size);
+      __rebind_al.deallocate(static_cast<__gen_aligned_block*>(__ptr), static_cast<__rebind_size_type>(__block_count));
+    } else {
+      // Retrieves the stateful allocator stored in the allocated block
+      __rebind* const __rebind_al_ptr = reinterpret_cast<__rebind*>(
+          (reinterpret_cast<uintptr_t>(__ptr) + __size + alignof(__rebind) - 1) & ~(alignof(__rebind) - 1));
+      __rebind __rebind_al = std::move(*__rebind_al_ptr);
+      std::destroy_at(__rebind_al_ptr);
+
+      const size_t __block_count = __gen_aligned_block::__count(__size + sizeof(__rebind) + alignof(__rebind));
+      __rebind_al.deallocate(static_cast<__gen_aligned_block*>(__ptr), static_cast<__rebind_size_type>(__block_count));
+    }
+  }
+};
+
+template <>
+class __gen_promise_allocator<void> {
+private:
+  using __dealloc_fn = void (*)(void*, size_t) noexcept;
+
+  template <class _Allocator>
+  _LIBCPP_HIDE_FROM_ABI static void* __alloc(const _Allocator& __al, size_t __size) {
+    using __rebind           = allocator_traits<_Allocator>::template rebind_alloc<__gen_aligned_block>;
+    using __rebind_size_type = allocator_traits<__rebind>::size_type;
+
+    using __rebind_pointer = allocator_traits<__rebind>::pointer;
+    static_assert(is_pointer_v<__rebind_pointer>);
+
+    __rebind __rebind_al = static_cast<__rebind>(__al);
+    if constexpr (__gen_stateless_allocator<__rebind>) {
+      const size_t __block_count = __gen_aligned_block::__count(__size + sizeof(__dealloc_fn));
+      void* const __ptr          = __rebind_al.allocate(static_cast<__rebind_size_type>(__block_count));
+
+      // Stores the deallocation function in the allocated block
+      __dealloc_fn* const __dealloc_ptr = reinterpret_cast<__dealloc_fn*>(reinterpret_cast<uintptr_t>(__ptr) + __size);
+      *__dealloc_ptr                    = __dealloc<__rebind>;
+      return __ptr;
+    } else {
+      // Allocates enough blocks for the coroutine frame, the size of the stateful allocator, the alignment of the
+      // stateful allocator, and the deallocation function
+      const size_t __block_count =
+          __gen_aligned_block::__count(__size + sizeof(__dealloc_fn) + sizeof(__rebind) + alignof(__rebind));
+      void* const __ptr = __rebind_al.allocate(static_cast<__rebind_size_type>(__block_count));
+
+      // Stores the deallocation function in the allocated block
+      __dealloc_fn* const __dealloc_ptr = reinterpret_cast<__dealloc_fn*>(reinterpret_cast<uintptr_t>(__ptr) + __size);
+      *__dealloc_ptr                    = __dealloc<__rebind>;
+
+      // Stores the stateful allocator in the allocated block
+      __rebind* const __rebind_al_ptr = reinterpret_cast<__rebind*>(
+          (reinterpret_cast<uintptr_t>(__ptr) + sizeof(__dealloc_fn) + __size + alignof(__rebind) - 1) &
+          ~(alignof(__rebind) - 1));
+      std::construct_at(__rebind_al_ptr, std::move(__rebind_al));
+      return __ptr;
+    }
+  }
+
+  template <class _Rebind>
+  _LIBCPP_HIDE_FROM_ABI static void __dealloc(void* const __ptr, size_t __size) noexcept {
+    using __rebind_size_type = allocator_traits<_Rebind>::size_type;
+
+    if constexpr (__gen_stateless_allocator<_Rebind>) {
+      _Rebind __rebind_al;
+      const size_t __block_count = __gen_aligned_block::__count(__size + sizeof(__dealloc_fn));
+      __rebind_al.deallocate(static_cast<__gen_aligned_block*>(__ptr), static_cast<__rebind_size_type>(__block_count));
+    } else {
+      // Retrieves the stateful allocator stored in the allocated block
+      _Rebind* const __rebind_al_ptr = reinterpret_cast<_Rebind*>(
+          (reinterpret_cast<uintptr_t>(__ptr) + __size + sizeof(__dealloc_fn) + alignof(_Rebind) - 1) &
+          ~(alignof(_Rebind) - 1));
+      _Rebind __rebind_al = std::move(*__rebind_al_ptr);
+      std::destroy_at(__rebind_al_ptr);
+
+      const size_t __block_count =
+          __gen_aligned_block::__count(__size + sizeof(__dealloc_fn) + sizeof(_Rebind) + alignof(_Rebind));
+      __rebind_al.deallocate(static_cast<__gen_aligned_block*>(__ptr), static_cast<__rebind_size_type>(__block_count));
+    }
+  }
+
+public:
+  _LIBCPP_HIDE_FROM_ABI void* operator new(size_t __size) {
+    void* const __ptr      = ::operator new(__size + sizeof(__dealloc_fn));
+    __dealloc_fn __dealloc = [](void* const __dealloc_ptr, const size_t __dealloc_size) static noexcept -> void {
+      ::operator delete(__dealloc_ptr, __dealloc_size + sizeof(__dealloc_fn));
+    };
+
+    __dealloc_fn* const __dealloc_ptr = reinterpret_cast<__dealloc_fn*>(reinterpret_cast<uintptr_t>(__ptr) + __size);
+    *__dealloc_ptr                    = __dealloc;
+    return __ptr;
+  }
+
+  template <class _Allocator, class... _Args>
+  _LIBCPP_HIDE_FROM_ABI void* operator new(size_t __size, allocator_arg_t, const _Allocator& __al, const _Args&...) {
+    return __alloc(__al, __size);
+  }
+
+  template <class _This, class _Allocator, class... _Args>
+  _LIBCPP_HIDE_FROM_ABI void*
+  operator new(size_t __size, const _This&, allocator_arg_t, const _Allocator& __al, const _Args&...) {
+    return __alloc(__al, __size);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI static void operator delete(void* const __ptr, size_t __size) noexcept {
+    __dealloc_fn* const __dealloc_ptr = reinterpret_cast<__dealloc_fn*>(reinterpret_cast<uintptr_t>(__ptr) + __size);
+    const __dealloc_fn __dealloc      = *__dealloc_ptr;
+    __dealloc(__ptr, __size);
+  }
+};
+
+template <class _Ref, class _Val>
+class __gen_iter {
+private:
+  using __val = __gen_val<_Ref, _Val>;
+  using __ref = __gen_ref<_Ref, _Val>;
+
+public:
+  using value_type      = __val;
+  using difference_type = ptrdiff_t;
+
+  _LIBCPP_HIDE_FROM_ABI explicit __gen_iter(
+      coroutine_handle<__gen_promise_base<__gen_yielded<_Ref, _Val>>> __coroutine) noexcept
+      : __coroutine_{__coroutine} {}
+
+  _LIBCPP_HIDE_FROM_ABI __gen_iter(__gen_iter&& __other) noexcept
+      : __coroutine_{std::exchange(__other.__coroutine_, {})} {}
+
+  _LIBCPP_HIDE_FROM_ABI __gen_iter& operator=(__gen_iter&& __other) noexcept {
+    __coroutine_ = std::exchange(__other.__coroutine_, {});
+    return *this;
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI __ref operator*() const noexcept(is_nothrow_copy_constructible_v<__ref>) {
+    return static_cast<__ref>(*__coroutine_.promise().__value_ptr());
+  }
+
+  _LIBCPP_HIDE_FROM_ABI __gen_iter& operator++() {
+    __coroutine_.promise().__active().resume();
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void operator++(int) { ++*this; }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend bool operator==(const __gen_iter& __iter, default_sentinel_t) noexcept {
+    return __iter.__coroutine_.done();
+  }
+
+private:
+  coroutine_handle<__gen_promise_base<__gen_yielded<_Ref, _Val>>> __coroutine_;
+};
+
+template <class _Ref, class _Val = void, class _Allocator = void>
+class generator : public ranges::view_interface<generator<_Ref, _Val, _Allocator>> {
+private:
+  using __val = __gen_val<_Ref, _Val>;
+  static_assert(same_as<remove_cvref_t<__val>, __val> && is_object_v<__val>);
----------------
mordante wrote:

Can you add .verify tests for these Mandates?

https://github.com/llvm/llvm-project/pull/92213


More information about the libcxx-commits mailing list