diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 3db190620a6..ba7134e0f83 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -81,6 +81,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/fstream ${CMAKE_CURRENT_LIST_DIR}/inc/functional ${CMAKE_CURRENT_LIST_DIR}/inc/future + ${CMAKE_CURRENT_LIST_DIR}/inc/generator ${CMAKE_CURRENT_LIST_DIR}/inc/hash_map ${CMAKE_CURRENT_LIST_DIR}/inc/hash_set ${CMAKE_CURRENT_LIST_DIR}/inc/header-units.json diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index ad3e8286d98..90a110d741c 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -86,6 +86,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/coroutine b/stl/inc/coroutine index c41e6b36f59..94933610fbe 100644 --- a/stl/inc/coroutine +++ b/stl/inc/coroutine @@ -92,12 +92,12 @@ private: void* _Ptr = nullptr; }; -_EXPORT_STD template +_EXPORT_STD template struct coroutine_handle { constexpr coroutine_handle() noexcept = default; constexpr coroutine_handle(nullptr_t) noexcept {} - _NODISCARD static coroutine_handle from_promise(_Promise& _Prom) noexcept { // strengthened + _NODISCARD static coroutine_handle from_promise(_CoroPromise& _Prom) noexcept { // strengthened const auto _Prom_ptr = const_cast(static_cast(_STD addressof(_Prom))); const auto _Frame_ptr = __builtin_coro_promise(_Prom_ptr, 0, true); coroutine_handle _Result; @@ -144,8 +144,8 @@ struct coroutine_handle { __builtin_coro_destroy(_Ptr); } - _NODISCARD _Promise& promise() const noexcept { // strengthened - return *reinterpret_cast<_Promise*>(__builtin_coro_promise(_Ptr, 0, false)); + _NODISCARD _CoroPromise& promise() const noexcept { // strengthened + return *reinterpret_cast<_CoroPromise*>(__builtin_coro_promise(_Ptr, 0, false)); } private: @@ -184,10 +184,10 @@ _NODISCARD constexpr bool operator>=(const coroutine_handle<> _Left, const corou } #endif // ^^^ !_HAS_CXX20 ^^^ -template -struct hash> { +template +struct hash> { _NODISCARD _STATIC_CALL_OPERATOR size_t operator()( - const coroutine_handle<_Promise>& _Coro) _CONST_CALL_OPERATOR noexcept { + const coroutine_handle<_CoroPromise>& _Coro) _CONST_CALL_OPERATOR noexcept { return _Hash_representation(_Coro.address()); } }; diff --git a/stl/inc/generator b/stl/inc/generator new file mode 100644 index 00000000000..8e5d94619d9 --- /dev/null +++ b/stl/inc/generator @@ -0,0 +1,606 @@ +// generator standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef _GENERATOR_ +#define _GENERATOR_ +#include +#if _STL_COMPILER_PREPROCESSOR + +#if !_HAS_CXX23 +_EMIT_STL_WARNING(STL4038, "The contents of are available only with C++23 or later."); +#else // ^^^ !_HAS_CXX23 / _HAS_CXX23 vvv + +#include +#include +#include +#ifdef __cpp_lib_byte +#include +#endif // defined(__cpp_lib_byte) + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +// TRANSITION, non-_Ugly attribute tokens +#pragma push_macro("empty_bases") +#undef empty_bases + +_STD_BEGIN + +struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) _Aligned_block { + unsigned char _Pad[__STDCPP_DEFAULT_NEW_ALIGNMENT__]; +}; + +template +constexpr bool _Stateless_allocator = default_initializable<_Alloc> && allocator_traits<_Alloc>::is_always_equal::value; + +_EXPORT_STD template +class generator; + +namespace _Gen_detail { + template + concept _Valid_allocator = same_as<_Alloc, void> || is_pointer_v::pointer>; + + template + _NODISCARD _Alloc* _Get_allocator_address(void* const _Ptr, const size_t _Size) noexcept { + _STL_INTERNAL_STATIC_ASSERT(!_Stateless_allocator<_Alloc>); + const auto _Al_address = + (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) & ~(alignof(_Alloc) - 1); + return reinterpret_cast<_Alloc*>(_Al_address); + } + + template + class _Promise_allocator { // statically specified allocator type + private: + using _Alloc = _Rebind_alloc_t<_Proto_allocator, _Aligned_block>; + using _Alloc_size_type = allocator_traits<_Alloc>::size_type; + + _NODISCARD static size_t _Allocation_block_size(const size_t _Size) noexcept { + // Compute the number of _Aligned_blocks needed to store a coroutine + // frame of _Size bytes and, if necessary, an _Alloc. + if constexpr (_Stateless_allocator<_Alloc>) { + // allocator is stateless, we need not store it + return (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); + } else { + // allocator is stateful, we need storage for it + constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); + return (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); + } + } + + _NODISCARD_RAW_PTR_ALLOC static void* _Allocate(_Alloc _Al, const size_t _Size) { + // memory layout: + // |--- coroutine frame --|- alignment padding -|---- _Alloc -----| + // |--- always present ---|----------- stateful only -------------| + // all rounded up to an integral number of _Aligned_blocks + + const size_t _Size_in_blocks = _Allocation_block_size(_Size); + void* const _Ptr = _Al.allocate(_Convert_size<_Alloc_size_type>(_Size_in_blocks)); + + if constexpr (!_Stateless_allocator<_Alloc>) { + // store only stateful allocators + const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size); + _STD construct_at(_Al_address, _STD move(_Al)); + } + + return _Ptr; + } + + _NODISCARD static _Alloc _Get_allocator( + [[maybe_unused]] void* const _Ptr, [[maybe_unused]] const size_t _Size) noexcept { + // recreate or retrieve a copy of the allocator used to allocate _Ptr + if constexpr (_Stateless_allocator<_Alloc>) { + return _Alloc{}; + } else { + const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size); + return *_Al_address; + } + } + + public: + _NODISCARD_RAW_PTR_ALLOC static void* operator new(const size_t _Size) + requires default_initializable<_Alloc> + { + return _Allocate(_Alloc{}, _Size); + } + + template + requires convertible_to + _NODISCARD_RAW_PTR_ALLOC static void* operator new( + const size_t _Size, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { + return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); + } + + template + requires convertible_to + _NODISCARD_RAW_PTR_ALLOC static void* operator new( + const size_t _Size, const _This&, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { + return _Allocate(static_cast<_Alloc>(static_cast<_Proto_allocator>(_Al)), _Size); + } + + static void operator delete(void* const _Ptr, const size_t _Size) noexcept { + _Alloc _Al = _Get_allocator(_Ptr, _Size); + const size_t _Size_in_blocks = _Allocation_block_size(_Size); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), static_cast<_Alloc_size_type>(_Size_in_blocks)); + } + }; + + template <> + class _Promise_allocator { // type-erased allocator + private: + using _Dealloc_fn = void(__stdcall*)(void*, size_t) _NOEXCEPT_FNPTR; + + template + _NODISCARD static size_t _Allocation_block_size(const size_t _Size) noexcept { + size_t _Bytes = _Size + sizeof(_Dealloc_fn); + if constexpr (_Stateless_allocator<_Alloc>) { + _Bytes += sizeof(_Aligned_block) - 1; + } else { + constexpr size_t _Align = (_STD max)(alignof(_Alloc), sizeof(_Aligned_block)); + _Bytes += sizeof(_Alloc) + _Align - 1; + } + return _Bytes / sizeof(_Aligned_block); + } + + template + static void __stdcall _Dealloc_stateless(void* const _Ptr, const size_t _Size) noexcept { + _STL_INTERNAL_STATIC_ASSERT(_Stateless_allocator<_Alloc>); + _Alloc _Al{}; + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Alloc_size); + } + + template + static void __stdcall _Dealloc_stateful(void* const _Ptr, const size_t _Size) noexcept { + _STL_INTERNAL_STATIC_ASSERT(!_Stateless_allocator<_Alloc>); + const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size + sizeof(_Dealloc_fn)); + auto& _Stored_al = *_Al_address; + _Alloc _Al{_STD move(_Stored_al)}; + _Stored_al.~_Alloc(); + + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = static_cast::size_type>(_Size_in_blocks); + _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Alloc_size); + } + + static void __stdcall _Dealloc_default(void* const _Ptr, const size_t _Size) noexcept { + ::operator delete(_Ptr, _Size + sizeof(_Dealloc_fn)); + } + + template + _NODISCARD_RAW_PTR_ALLOC static void* _Allocate(const _ProtoAlloc& _Proto, const size_t _Size) { + // memory layout: + // |--- coroutine frame (_Size bytes) ---|--- _Dealloc_fn ---|-- alignment padding --|--- _Alloc ---| + // |-------------------- always present -----(not aligned)---|------------ stateful only -----------| + // all rounded up to an integral number of _Aligned_blocks + + using _Alloc = _Rebind_alloc_t<_ProtoAlloc, _Aligned_block>; + using _Alloc_size_type = allocator_traits<_Alloc>::size_type; + + _Alloc _Al{_Proto}; + const size_t _Size_in_blocks = _Allocation_block_size<_Alloc>(_Size); + const auto _Alloc_size = _Convert_size<_Alloc_size_type>(_Size_in_blocks); + void* const _Ptr = _Al.allocate(_Alloc_size); + + if constexpr (_Stateless_allocator<_Alloc>) { + // don't store stateless allocator + const _Dealloc_fn _Dealloc = _Dealloc_stateless<_Alloc>; + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); + return _Ptr; + } else { + // store stateful allocator + const _Dealloc_fn _Dealloc = _Dealloc_stateful<_Alloc>; + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); + + const auto _Al_address = _Get_allocator_address<_Alloc>(_Ptr, _Size + sizeof(_Dealloc_fn)); + _STD construct_at(_Al_address, _STD move(_Al)); + return _Ptr; + } + } + + public: + _NODISCARD_RAW_PTR_ALLOC static void* operator new(const size_t _Size) { + void* const _Ptr = ::operator new(_Size + sizeof(_Dealloc_fn)); + const _Dealloc_fn _Dealloc = _Dealloc_default; + _CSTD memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); + return _Ptr; + } + + template + _NODISCARD_RAW_PTR_ALLOC static void* operator new( + const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { + static_assert(_Valid_allocator<_Alloc>, "generator allocators must use raw pointers " + "(N4988 [coro.generator.class]/1.1)"); + return _Allocate(_Al, _Size); + } + + template + _NODISCARD_RAW_PTR_ALLOC static void* operator new( + const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { + static_assert(_Valid_allocator<_Alloc>, "generator allocators must use raw pointers " + "(N4988 [coro.generator.class]/1.1)"); + return _Allocate(_Al, _Size); + } + + static void operator delete(void* const _Ptr, const size_t _Size) noexcept { + _Dealloc_fn _Dealloc; + _CSTD memcpy(&_Dealloc, static_cast(_Ptr) + _Size, sizeof(_Dealloc_fn)); + _Dealloc(_Ptr, _Size); + } + }; + + template + using _Value_t = conditional_t, remove_cvref_t<_Rty>, _Vty>; + template + using _Reference_t = conditional_t, _Rty&&, _Rty>; + template + using _Yield_t = conditional_t, _Ref, const _Ref&>; + + template + struct _Iter_provider { + class _Iterator; + }; + + template + class _Promise_base { + public: + _STL_INTERNAL_STATIC_ASSERT(is_reference_v<_Yielded>); + +#ifndef _PREFAST_ // TRANSITION, VSO-1662733 + _NODISCARD +#endif // ^^^ no workaround ^^^ + suspend_always initial_suspend() const noexcept { + return {}; + } + + _NODISCARD auto final_suspend() noexcept { + return _Final_awaiter{}; + } + + _NODISCARD suspend_always yield_value(_Yielded _Val) noexcept { + _Ptr = _STD addressof(_Val); + return {}; + } + + _NODISCARD auto yield_value(const remove_reference_t<_Yielded>& _Val) + noexcept(is_nothrow_constructible_v, + const remove_reference_t<_Yielded>&>) /* strengthened */ + requires is_rvalue_reference_v<_Yielded> + && constructible_from, const remove_reference_t<_Yielded>&> + { + return _Element_awaiter{_Val}; + } + + template + requires same_as<_Yield_t<_Reference_t<_Rty, _Vty>>, _Yielded> + _NODISCARD auto yield_value(_RANGES elements_of&&, _Unused> _Elem) noexcept { + using _Nested_awaitable = _Nested_awaitable_provider<_Rty, _Vty, _Alloc>::_Awaitable; + return _Nested_awaitable{_STD move(_Elem.range)}; + } + + template <_RANGES input_range _Rng, class _Alloc> + requires convertible_to<_RANGES range_reference_t<_Rng>, _Yielded> + _NODISCARD auto yield_value(_RANGES elements_of<_Rng, _Alloc> _Elem) { + using _Vty = _RANGES range_value_t<_Rng>; + using _Nested_awaitable = _Nested_awaitable_provider<_Yielded, _Vty, _Alloc>::_Awaitable; + + auto _Lambda = [](allocator_arg_t, _Alloc, _RANGES iterator_t<_Rng> _It, + const _RANGES sentinel_t<_Rng> _Se) -> generator<_Yielded, _Vty, _Alloc> { + for (; _It != _Se; ++_It) { + co_yield static_cast<_Yielded>(*_It); + } + }; + return _Nested_awaitable{ + _Lambda(allocator_arg, _Elem.allocator, _RANGES begin(_Elem.range), _RANGES end(_Elem.range))}; + } + + void await_transform() = delete; + + void return_void() const noexcept {} + + void unhandled_exception() { + if (const auto _Info = _Try_get_nest_info()) { + _Info->_Except = _STD current_exception(); + } else { + _RERAISE; + } + } + + private: + struct _Element_awaiter { + remove_cvref_t<_Yielded> _Val; + + explicit _Element_awaiter(const remove_reference_t<_Yielded>& _Val_) + noexcept(is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) + : _Val(_Val_) {} + + _NODISCARD constexpr bool await_ready() const noexcept { + return false; + } + + template + constexpr void await_suspend(coroutine_handle<_CoroPromise> _Handle) noexcept { +#ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Promise_base, _CoroPromise>); +#endif // ^^^ no workaround ^^^ + + _Promise_base& _Current = _Handle.promise(); + _Current._Ptr = _STD addressof(_Val); + } + + constexpr void await_resume() const noexcept {} + }; + + // generator's stack of nested coroutines is realized as a linked list of promises. The "root" promise is the + // original that existed before any nested yields. The "top" promise is the one currently being iterated over. + // Iterators hold handles for the root's coroutine and the root promise holds a link to the top promise so + // iterators can resume it to generate the next element when incremented. Each promise keeps a link to the root + // so they can update the top link as promises are pushed and popped. It looks a bit like: + // + // +--------------------------- Top ---------------------------+ + // v | + // +=====+ +=====+ +=====+ +=====+ + // | |-- Parent -->| |-- Parent -->| |-- Parent -->| | + // +=====+ +=====+ +=====+ +=====+ + // | | | ^ + // +------- Root ------+------- Root ------+------- Root ------+ + // + // The parent and root links are actually stored out-of-line in a _Nest_info. Since a promise is either a root + // or is nested, the single promise member _Data can be used to store either a top link or a pointer to a + // _Nest_info. + + struct _Nest_info { + exception_ptr _Except; + coroutine_handle<_Promise_base> _Parent; + coroutine_handle<_Promise_base> _Root; + }; + + struct _Final_awaiter { + _NODISCARD bool await_ready() const noexcept { + return false; + } + + template + _NODISCARD coroutine_handle<> await_suspend(coroutine_handle<_CoroPromise> _Handle) const noexcept { + // Resume _Handle's parent coroutine, if any. +#ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Promise_base, _CoroPromise>); +#endif // ^^^ no workaround ^^^ + + if (const auto _Info = _Handle.promise()._Try_get_nest_info()) { + coroutine_handle<_Promise_base> _Cont = _Info->_Parent; + _Info->_Root.promise()._Set_top(_Cont); + return _Cont; + } + + return _STD noop_coroutine(); + } + + void await_resume() const noexcept {} + }; + + template + struct _Nested_awaitable_provider { + struct _Awaitable { + _STL_INTERNAL_STATIC_ASSERT(same_as<_Yield_t<_Reference_t<_Rty, _Vty>>, _Yielded>); + + _Nest_info _Nested; + generator<_Rty, _Vty, _Alloc> _Gen; + + explicit _Awaitable(generator<_Rty, _Vty, _Alloc>&& _Gen_) noexcept : _Gen(_STD move(_Gen_)) {} + + _NODISCARD bool await_ready() const noexcept { + return !_Gen._Coro; + } + + template + _NODISCARD coroutine_handle<_Promise_base> await_suspend( + coroutine_handle<_CoroPromise> _Current) noexcept { + // Push _Gen's coroutine onto the coroutine stack that _Current is at the top of. +#ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 + _STL_INTERNAL_STATIC_ASSERT(is_pointer_interconvertible_base_of_v<_Promise_base, _CoroPromise>); +#endif // ^^^ no workaround ^^^ + auto _Target = coroutine_handle<_Promise_base>::from_address(_Gen._Coro.address()); + _Nested._Parent = coroutine_handle<_Promise_base>::from_address(_Current.address()); + if (const auto _Parent_nest_info = _Nested._Parent.promise()._Try_get_nest_info()) { + _Nested._Root = _Parent_nest_info->_Root; + } else { + _Nested._Root = _Nested._Parent; + } + _Nested._Root.promise()._Set_top(_Target); + _Target.promise()._Set_nest_info(_STD addressof(_Nested)); + return _Target; + } + + void await_resume() { + if (_Nested._Except) { + _STD rethrow_exception(_STD move(_Nested._Except)); + } + } + }; + }; + + _NODISCARD _Nest_info* _Try_get_nest_info() const noexcept { + if ((_Data & 1U) != 0) { + return reinterpret_cast<_Nest_info*>(_Data ^ 1U); + } + + return nullptr; + } + + _NODISCARD coroutine_handle<_Promise_base> _Get_top() const noexcept { + _STL_INTERNAL_CHECK((_Data & 1U) == 0); + return coroutine_handle<_Promise_base>::from_address(reinterpret_cast(_Data)); + } + + void _Set_nest_info(_Nest_info* _Info) noexcept { + _Data = reinterpret_cast(_Info) | 1U; + } + + void _Set_top(coroutine_handle<_Promise_base> _Top) noexcept { + _Data = reinterpret_cast(_Top.address()); + } + + template + friend struct _Iter_provider; + + // Least significant bit of `_Data` indicates stored information: + // LSB 0: `_Data` is a top coroutine handle, + // LSB 1: `_Data ^ 1U` is a pointer to an object of type `_Nest_info`. + uintptr_t _Data = reinterpret_cast(coroutine_handle<_Promise_base>::from_promise(*this).address()); + add_pointer_t<_Yielded> _Ptr = nullptr; + }; + + struct _Secret_tag { + explicit _Secret_tag() = default; + }; + + template + class _Iter_provider<_Value, _Ref>::_Iterator { + public: + using value_type = _Value; + using difference_type = ptrdiff_t; + + _Iterator(_Iterator&& _That) noexcept : _Coro{_STD exchange(_That._Coro, {})} {} + + _Iterator& operator=(_Iterator&& _That) noexcept { + _Coro = _STD exchange(_That._Coro, {}); + return *this; + } + + _NODISCARD _Ref operator*() const + noexcept(noexcept(static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr))) /* strengthened */ { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(!_Coro.done(), "Can't dereference generator end iterator"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + return static_cast<_Ref>(*_Coro.promise()._Get_top().promise()._Ptr); + } + + _Iterator& operator++() { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(!_Coro.done(), "Can't increment generator end iterator"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + _Coro.promise()._Get_top().resume(); + return *this; + } + + void operator++(int) { + ++*this; + } + + _NODISCARD friend bool operator==(const _Iterator& _It, default_sentinel_t) noexcept /* strengthened */ { + return _It._Coro.done(); + } + + private: + template + friend class _STD generator; + + explicit _Iterator(_Secret_tag, coroutine_handle<_Promise_base<_Yield_t<_Ref>>> _Coro_) noexcept + : _Coro{_Coro_} {} + + coroutine_handle<_Promise_base<_Yield_t<_Ref>>> _Coro; + }; +} // namespace _Gen_detail + +_EXPORT_STD template +class generator : public _RANGES view_interface> { +private: + static_assert(_Gen_detail::_Valid_allocator<_Alloc>, + "generator allocators must use raw pointers (N4988 [coro.generator.class]/1.1)"); + + using _Value = _Gen_detail::_Value_t<_Rty, _Vty>; + static_assert(same_as, _Value> && is_object_v<_Value>, + "generator's value type must be a cv-unqualified object type (N4988 [coro.generator.class]/1.2)"); + + using _Ref = _Gen_detail::_Reference_t<_Rty, _Vty>; + static_assert( + is_reference_v<_Ref> || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), + "generator's selected reference type must be an actual reference type or a cv-unqualified copy-constructible " + "object type (N4988 [coro.generator.class]/1.3)"); + + using _RRef = conditional_t, remove_reference_t<_Ref>&&, _Ref>; + + static_assert(common_reference_with<_Ref&&, _Value&> && common_reference_with<_Ref&&, _RRef&&> + && common_reference_with<_RRef&&, const _Value&>, + "generator's iterator type must model indirectly_readable, but that's impossible with the selected value and " + "reference types (N4988 [coro.generator.class]/1.4)"); + +public: + using yielded = _Gen_detail::_Yield_t<_Ref>; + + friend _Gen_detail::_Promise_base; + + struct __declspec(empty_bases) promise_type : _Gen_detail::_Promise_allocator<_Alloc>, + _Gen_detail::_Promise_base { + _NODISCARD generator get_return_object() noexcept { + return generator{_Gen_detail::_Secret_tag{}, coroutine_handle::from_promise(*this)}; + } + }; + _STL_INTERNAL_STATIC_ASSERT(is_standard_layout_v); +#ifdef __cpp_lib_is_pointer_interconvertible // TRANSITION, LLVM-48860 + _STL_INTERNAL_STATIC_ASSERT( + is_pointer_interconvertible_base_of_v<_Gen_detail::_Promise_base, promise_type>); +#endif // ^^^ no workaround ^^^ + + generator(generator&& _That) noexcept : _Coro(_STD exchange(_That._Coro, {})) {} + + ~generator() { + if (_Coro) { + _Coro.destroy(); + } + } + + generator& operator=(generator _That) noexcept { + _STD swap(_Coro, _That._Coro); + return *this; + } + + _NODISCARD _Gen_detail::_Iter_provider<_Value, _Ref>::_Iterator begin() { + // Pre: _Coro is suspended at its initial suspend point +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Coro, "Can't call begin on moved-from generator"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + _Coro.resume(); + return typename _Gen_detail::_Iter_provider<_Value, _Ref>::_Iterator{_Gen_detail::_Secret_tag{}, + coroutine_handle<_Gen_detail::_Promise_base>::from_address(_Coro.address())}; + } + + _NODISCARD default_sentinel_t end() const noexcept { + return default_sentinel; + } + +private: + coroutine_handle _Coro = nullptr; + // The stack of coroutine handles depicted in the Standard is realized by a linked structure of promises and + // awaitable objects, so all necessary storage is allocated in coroutine frames. See the description in the body of + // _Gen_detail::_Promise_base. + + explicit generator(_Gen_detail::_Secret_tag, coroutine_handle _Coro_) noexcept : _Coro(_Coro_) {} +}; + +#ifdef __cpp_lib_byte +namespace pmr { + _EXPORT_STD template + using generator = _STD generator<_Rty, _Vty, polymorphic_allocator<>>; +} // namespace pmr +#endif // defined(__cpp_lib_byte) +_STD_END + +// TRANSITION, non-_Ugly attribute tokens +#pragma pop_macro("empty_bases") + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) + +#endif // ^^^ _HAS_CXX23 ^^^ +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _GENERATOR_ diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index b2a2a6e7b9c..3c5bc57ce3d 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -75,6 +75,7 @@ "fstream", "functional", "future", + "generator", // "hash_map", // non-Standard, will be removed soon // "hash_set", // non-Standard, will be removed soon "initializer_list", diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 4944352f3a6..ad1530dab62 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2588,6 +2588,28 @@ template concept _Transparent = _Is_transparent_v<_Ty>; #endif // _HAS_CXX20 +#if _HAS_CXX23 +namespace ranges { +#if defined(__cpp_lib_byte) + _EXPORT_STD template > +#else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv + _EXPORT_STD template +#endif // ^^^ !defined(__cpp_lib_byte) ^^^ + struct elements_of { + /* [[no_unique_address]] */ _Rng range; + /* [[no_unique_address]] */ _Alloc allocator{}; + }; + +#if defined(__cpp_lib_byte) + template > + elements_of(_Rng&&, _Alloc = _Alloc()) -> elements_of<_Rng&&, _Alloc>; +#else // ^^^ defined(__cpp_lib_byte) / !defined(__cpp_lib_byte) vvv + template + elements_of(_Rng&&, _Alloc) -> elements_of<_Rng&&, _Alloc>; +#endif // ^^^ !defined(__cpp_lib_byte) ^^^ +} // namespace ranges +#endif // _HAS_CXX23 + template _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by both to_string and thread::id output // format _UVal into buffer *ending at* _RNext diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index bbf9cbd75f2..12f16f58c1c 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -379,6 +379,7 @@ // P2474R2 views::repeat // P2494R2 Relaxing Range Adaptors To Allow Move-Only Types // P2499R0 string_view Range Constructor Should Be explicit +// P2502R2 : Synchronous Coroutine Generator For Ranges // P2505R5 Monadic Functions For expected // P2539R4 Synchronizing print() With The Underlying Stream // P2540R1 Empty Product For Certain Views @@ -391,6 +392,7 @@ // P2693R1 Formatting thread::id And stacktrace // P2713R1 Escaping Improvements In std::format // P2763R1 Fixing layout_stride's Default Constructor For Fully Static Extents +// P2787R1 pmr::generator // P2833R2 Freestanding Library: inout expected span // (except for __cpp_lib_span which also covers C++26 span::at) // P2836R1 basic_const_iterator Should Follow Its Underlying Type's Convertibility @@ -1764,6 +1766,7 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect #define __cpp_lib_forward_like 202207L #define __cpp_lib_freestanding_expected 202311L #define __cpp_lib_freestanding_mdspan 202311L +#define __cpp_lib_generator 202207L #define __cpp_lib_invoke_r 202106L #define __cpp_lib_ios_noreplace 202207L #define __cpp_lib_is_scoped_enum 202011L diff --git a/stl/modules/std.ixx b/stl/modules/std.ixx index fc4d4c74fec..23fa034bf35 100644 --- a/stl/modules/std.ixx +++ b/stl/modules/std.ixx @@ -69,6 +69,9 @@ export module std; #include #include #include +#if _HAS_CXX23 +#include +#endif // _HAS_CXX23 #include #include #include diff --git a/tests/std/include/test_generator_support.hpp b/tests/std/include/test_generator_support.hpp new file mode 100644 index 00000000000..7c5211de05c --- /dev/null +++ b/tests/std/include/test_generator_support.hpp @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include +#include +#include +#include +#include + +template +struct gen_traits; + +template +struct gen_traits { + using generator = std::generator; + using value = std::remove_cvref_t; + using reference = R&&; + using yielded = reference; + + // Verify the default template arguments + static_assert(std::same_as, std::generator>); + static_assert(std::same_as, std::generator>); +}; +template +struct gen_traits { + using generator = std::generator; + using value = V; + using reference = R; + using yielded = std::conditional_t, R, const R&>; + + // Ditto verify default template arguments + static_assert(std::same_as, std::generator>); +}; +template +struct gen_traits : gen_traits { + using generator = std::generator; +}; + +template +using gen_value_t = gen_traits::value; + +template +using gen_reference_t = gen_traits::reference; + +template +class StatelessAlloc { +public: + using value_type = T; + using is_always_equal = AlwaysEqual; + using difference_type = DifferenceType; + using size_type = std::make_unsigned_t; + + StatelessAlloc() = default; + + template + StatelessAlloc(const StatelessAlloc&) {} + + T* allocate(const size_type s) { + return std::allocator{}.allocate(s); + } + + void deallocate(T* const p, const size_type n) noexcept { + std::allocator{}.deallocate(p, n); + } + + template + bool operator==(const StatelessAlloc&) const noexcept { + return true; + } +}; + +static_assert(std::default_initializable>); + +template +class StatefulAlloc { +public: + using value_type = T; + + explicit StatefulAlloc(int dom) noexcept : domain{dom} {} + + template + StatefulAlloc(const StatefulAlloc& that) noexcept : domain{that.domain} {} + + T* allocate(const size_t n) { + return std::allocator{}.allocate(n); + } + + void deallocate(T* const p, const size_t n) noexcept { + return std::allocator{}.deallocate(p, n); + } + + template + bool operator==(const StatefulAlloc& that) noexcept { + return domain == that.domain; + } + +private: + int domain; + + template + friend class StatefulAlloc; +}; + +static_assert(!std::default_initializable>); + +struct MoveOnly { + MoveOnly() = default; + MoveOnly(const MoveOnly&) = delete; + MoveOnly& operator=(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) = default; + MoveOnly& operator=(MoveOnly&&) = default; +}; + +static_assert(std::movable); +static_assert(!std::copyable); + +struct Immovable { + Immovable() = default; + Immovable(Immovable&&) = delete; + Immovable& operator=(Immovable&&) = delete; +}; + +static_assert(!std::movable); + +template +class Proxy { +public: + Proxy(const T&) {} +}; + +template +class Proxy { +public: + Proxy(const T& _value_) : value(_value_) {} + + bool operator==(const Proxy& other) const { + return value == other.value; + } + + bool operator==(const T& x) const { + return value == x; + } + +private: + const T& value; +}; diff --git a/tests/std/include/test_header_units_and_modules.hpp b/tests/std/include/test_header_units_and_modules.hpp index c3779215895..bf79ffccab7 100644 --- a/tests/std/include/test_header_units_and_modules.hpp +++ b/tests/std/include/test_header_units_and_modules.hpp @@ -284,6 +284,20 @@ void test_future() { assert(f.get() == 1729); } +#if TEST_STANDARD >= 23 +void test_generator() { + using namespace std; + puts("Testing ."); + auto some_ints = [](int hi) -> generator { + for (int i = 0; i < hi; ++i) { + co_yield i; + } + }; + constexpr int bound = 42; + assert(ranges::equal(some_ints(bound), views::iota(0, bound))); +} +#endif // TEST_STANDARD >= 23 + void test_initializer_list() { using namespace std; puts("Testing ."); @@ -1182,6 +1196,9 @@ void all_cpp_header_tests() { test_fstream(); test_functional(); test_future(); +#if TEST_STANDARD >= 23 + test_generator(); +#endif // TEST_STANDARD >= 23 test_initializer_list(); test_iomanip(); test_ios(); diff --git a/tests/std/test.lst b/tests/std/test.lst index fde8f2a1d2c..e0fc332c98e 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -670,6 +670,10 @@ tests\P2467R1_exclusive_mode_fstreams tests\P2474R2_views_repeat tests\P2474R2_views_repeat_death tests\P2494R2_move_only_range_adaptors +tests\P2502R2_generator +tests\P2502R2_generator_death +tests\P2502R2_generator_iterator +tests\P2502R2_generator_promise tests\P2505R5_monadic_functions_for_std_expected tests\P2510R3_text_formatting_pointers tests\P2517R1_apply_conditional_noexcept diff --git a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc index d4960889f74..f01082994e8 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc +++ b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc @@ -28,6 +28,7 @@ "fstream", "functional", "future", + "generator", "initializer_list", "iomanip", "ios", diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index 1617b5e2859..a4e51a71043 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -36,6 +36,9 @@ import ; import ; import ; import ; +#if TEST_STANDARD >= 23 +import ; +#endif // TEST_STANDARD >= 23 import ; import ; import ; diff --git a/tests/std/tests/P2502R2_generator/env.lst b/tests/std/tests/P2502R2_generator/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2502R2_generator/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2502R2_generator/test.cpp b/tests/std/tests/P2502R2_generator/test.cpp new file mode 100644 index 00000000000..5ad637b4fb1 --- /dev/null +++ b/tests/std/tests/P2502R2_generator/test.cpp @@ -0,0 +1,418 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_generator_support.hpp" + +#pragma warning(disable : 28251) // Inconsistent annotation for 'new': this instance has no annotations. + +using namespace std; + +template +consteval bool static_checks() { + using G = Traits::generator; + static_assert(derived_from>); + + // Specializations of generator are move-only input-and-no-stronger views + static_assert(ranges::view); + static_assert(ranges::input_range); + static_assert(!ranges::forward_range); + + static_assert(!copy_constructible); + static_assert(!is_copy_assignable_v); + + static_assert(is_nothrow_destructible_v); + static_assert(is_nothrow_move_constructible_v); + static_assert(is_nothrow_move_assignable_v); + + // Verify the generator's associated types + static_assert(same_as, ValueType>); + static_assert(same_as, ptrdiff_t>); + static_assert(same_as, ReferenceType>); + static_assert(same_as, RvalueReferenceType>); + static_assert(same_as); + + // Verify end + static_assert(same_as>); + static_assert(same_as().end())>); + static_assert(noexcept(declval().end())); + static_assert(noexcept(declval().end())); + + // iterator properties are verified in P2502R2_generator_iterator + // promise properties are verified in P2502R2_generator_promise + + // Non-portable size check + static_assert(sizeof(G) == sizeof(void*)); + + return true; +} + +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, const int&, const int&&>()); +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, int&, int&&>()); + +static_assert(static_checks, int, int, int>()); +static_assert(static_checks, int, const int&, const int&&>()); +static_assert(static_checks, int, int&&, int&&>()); +static_assert(static_checks, int, int&, int&&>()); + +template +void test_one(generator g0, invocable> auto adaptor, ranges::input_range auto&& expected) + requires ranges::input_range +{ + static_assert(same_as, typename Traits::generator>); + static_assert(static_checks()); + + auto g1 = move(g0); + auto i = ranges::cbegin(expected); + for (auto&& x : adaptor(move(g1))) { + assert(i != ranges::cend(expected)); + assert(*i == x); + ++i; + // Verify iterator stays valid after move assignment + ranges::swap(g0, g1); + } + assert(i == ranges::cend(expected)); +} + +template +void test_one(generator g0, Ex&& expected) { + return test_one(move(g0), identity{}, forward(expected)); +} + +// Some simple end-to-end tests, mostly from the Working Draft or P2502R2 +generator ints(int start = 0) { + while (true) { + co_yield start++; + } +} + +template +generator, ranges::range_reference_t>, + tuple, ranges::range_value_t>> + co_zip(Rng1 r1, Rng2 r2) { + auto it1 = ranges::begin(r1); + auto it2 = ranges::begin(r2); + const auto end1 = ranges::end(r1); + const auto end2 = ranges::end(r2); + for (; it1 != end1 && it2 != end2; ++it1, ++it2) { + co_yield {*it1, *it2}; + } +} + +void zip_example() { + using V = tuple; + using R = tuple; + + auto g0 = co_zip(array{1, 2, 3}, vector{10, 20, 30, 40, 50}); + test_one, V, R, R>(move(g0), array{tuple{1, 10}, tuple{2, 20}, tuple{3, 30}}); + + g0 = co_zip(array{3, 2, 1}, vector{10, 20, 30, 40, 50}); + test_one, V, R, R>(move(g0), array{tuple{3, 10}, tuple{2, 20}, tuple{1, 30}}); +} + +template +generator co_upto(const int hi) { + assert(hi >= 0); + for (int i = 0; i < hi; ++i) { + co_yield i; + } +} + +void test_weird_reference_types() { + constexpr int n = 32; + { // Test mutable lvalue reference type + auto r = co_upto(n); + auto pos = r.begin(); + for (int i = 0; i < n / 2; ++i, ++*pos, ++pos) { + assert(pos != r.end()); + assert(*pos == 2 * i); + } + assert(pos == r.end()); + } + +#if !(defined(__EDG__) || (defined(__clang__) && defined(_M_IX86))) // TRANSITION, VSO-2254804 and LLVM-56507 + { // Test with mutable rvalue reference type + constexpr size_t segment_size = 16; + auto woof = []() -> generator&&> { + vector vec(segment_size); + + co_yield vec; // When we yield an lvalue... + assert(vec.size() == segment_size); // ... the caller moves from a copy. + + co_yield move(vec); // When we yield an rvalue... + assert(vec.size() == 0); // ... the caller moves from it. + }; + + for (auto vec : woof()) { // Intentionally by value + assert(vec.size() == segment_size); + } + } +#endif // ^^^ no workaround ^^^ +} + +#if !(defined(__EDG__) || (defined(__clang__) && defined(_M_IX86))) // TRANSITION, VSO-2254804 and LLVM-56507 +generator iota_repeater(const int hi, const int depth) { + if (depth > 0) { + co_yield ranges::elements_of(iota_repeater(hi, depth - 1)); + co_yield ranges::elements_of(iota_repeater(hi, depth - 1)); + } else { + co_yield ranges::elements_of(co_upto(hi)); + } +} + +void recursive_test() { + constexpr auto might_throw = []() -> generator { + co_yield 0; + throw runtime_error{"error"}; + }; + + constexpr auto nested_ints = [=]() -> generator { + try { + co_yield ranges::elements_of(might_throw()); + } catch (const runtime_error& e) { + assert(e.what() == "error"sv); + } + co_yield 1; + }; + + test_one, int, int&&, int&&>(iota_repeater(3, 2), array{0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2}); + test_one, int, int&&, int&&>(nested_ints(), array{0, 1}); +} + +void arbitrary_range_test() { + auto yield_arbitrary_ranges = []() -> generator { + co_yield ranges::elements_of(vector{40, 30, 20, 10}); + co_yield ranges::elements_of(views::iota(0, 4)); + forward_list fl{500, 400, 300}; + co_yield ranges::elements_of(fl); + }; + + test_one, int, const int&, const int&&>( + yield_arbitrary_ranges(), array{40, 30, 20, 10, 0, 1, 2, 3, 500, 400, 300}); +} + +#ifndef _M_CEE // TRANSITION, VSO-1659496 +template +struct holder { + T t; +}; + +struct incomplete; + +void adl_proof_test() { + using validator = holder*; + auto yield_range = []() -> generator { + co_yield ranges::elements_of( + views::repeat(nullptr, 42) | views::transform([](nullptr_t) { return validator{}; })); + }; + + using R = decltype(yield_range()); + static_assert(ranges::input_range); + + using It = ranges::iterator_t; + static_assert(same_as()), It*>); + + using Promise = R::promise_type; + static_assert(same_as()), Promise*>); + + size_t i = 0; + for (const auto elem : yield_range()) { + ++i; + assert(elem == nullptr); + } + assert(i == 42); +} +#endif // ^^^ no workaround ^^^ +#endif // ^^^ no workaround ^^^ + +// Verify behavior with unerased allocator types +void static_allocator_test() { + { + auto g = [](const int hi) -> generator> { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + test_one>, int, int, int>(g(1024), views::iota(0, 1024)); + } + + { + auto g = [](allocator_arg_t, StatelessAlloc, const int hi) -> generator> { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + test_one>, int, int, int>( + g(allocator_arg, {}, 1024), views::iota(0, 1024)); + } + +#ifndef __EDG__ // TRANSITION, VSO-1951821 + { + auto g = [](allocator_arg_t, StatefulAlloc, const int hi) -> generator> { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + test_one>, int, int, int>( + g(allocator_arg, StatefulAlloc{42}, 1024), views::iota(0, 1024)); + } +#endif // ^^^ no workaround ^^^ +} + +// Verify behavior with erased allocator types +void dynamic_allocator_test() { + auto g = [](allocator_arg_t, const auto&, const int hi) -> generator { + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + test_one, int, int&&, int&&>(g(allocator_arg, allocator{}, 1024), views::iota(0, 1024)); + test_one, int, int&&, int&&>(g(allocator_arg, StatelessAlloc{}, 1024), views::iota(0, 1024)); +#ifndef __EDG__ // TRANSITION, VSO-1951821 + test_one, int, int&&, int&&>( + g(allocator_arg, StatefulAlloc{1729}, 1024), views::iota(0, 1024)); +#endif // ^^^ no workaround ^^^ + pmr::synchronized_pool_resource pool; + test_one, int, int&&, int&&>( + g(allocator_arg, pmr::polymorphic_allocator<>{&pool}, 1024), views::iota(0, 1024)); +} + +static atomic allow_allocation{true}; + +void* operator new(const size_t n) { + if (allow_allocation) { + if (void* const result = malloc(n)) { + return result; + } + } + throw bad_alloc{}; +} + +void operator delete(void* const p) noexcept { + free(p); +} + +void operator delete(void* const p, size_t) noexcept { + free(p); +} + +void* operator new(const size_t n, const align_val_t al) { + if (allow_allocation) { + if (void* const result = ::_aligned_malloc(n, static_cast(al))) { + return result; + } + } + throw bad_alloc{}; +} + +void operator delete(void* const p, align_val_t) noexcept { + ::_aligned_free(p); +} + +void operator delete(void* const p, size_t, align_val_t) noexcept { + ::_aligned_free(p); +} + +class malloc_resource final : public pmr::memory_resource { +private: + void* do_allocate(size_t bytes, size_t align) override { + assert(align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__); + if (bytes == 0) { + bytes = 1; + } + + if (void* const result = malloc(bytes)) { + return result; + } + throw bad_alloc{}; + } + + void do_deallocate(void* ptr, size_t, size_t align) noexcept override { + assert(align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__); + free(ptr); + } + + bool do_is_equal(const memory_resource& that) const noexcept override { + return typeid(malloc_resource) == typeid(that); + } +}; + +void pmr_generator_test() { + // Verify alias template + static_assert(same_as, generator>>); + static_assert(same_as, generator>>); + static_assert(same_as, generator>>); + + // Simple end-to-end test + malloc_resource mr{}; + auto g = [&mr](allocator_arg_t, pmr::polymorphic_allocator<> alloc, const int hi) -> pmr::generator { + assert(alloc.resource() == &mr); + + constexpr size_t n = 64; + int some_ints[n]; + for (int i = 0; i < hi; ++i) { + co_yield some_ints[i % n] = i; + } + }; + + allow_allocation = false; + test_one>, int, int, int>( + g(allocator_arg, pmr::polymorphic_allocator<>{&mr}, 1024), views::iota(0, 1024)); + allow_allocation = true; +} + +int main() { + // End-to-end tests + test_one, int, int&&, int&&>(ints(), views::take(3), array{0, 1, 2}); + assert(ranges::equal(co_upto(6), views::iota(0, 6))); + zip_example(); + test_weird_reference_types(); +#if !(defined(__EDG__) || (defined(__clang__) && defined(_M_IX86))) // TRANSITION, VSO-2254804 and LLVM-56507 + recursive_test(); + arbitrary_range_test(); + +#ifndef _M_CEE // TRANSITION, VSO-1659496 + // Verify generation of a range of pointers-to-incomplete + adl_proof_test(); +#endif // ^^^ no workaround ^^^ +#endif // ^^^ no workaround ^^^ + + // Allocator tests + static_allocator_test(); + dynamic_allocator_test(); + pmr_generator_test(); +} diff --git a/tests/std/tests/P2502R2_generator_death/env.lst b/tests/std/tests/P2502R2_generator_death/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2502R2_generator_death/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2502R2_generator_death/test.cpp b/tests/std/tests/P2502R2_generator_death/test.cpp new file mode 100644 index 00000000000..b45703596ee --- /dev/null +++ b/tests/std/tests/P2502R2_generator_death/test.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include + +#include + +std::generator gen() { + co_return; +} + +void test_begin_after_initial_suspend_point() { + auto g = gen(); + (void) g.begin(); + (void) g.begin(); +} + +void test_begin_after_moving_from() { + auto g = gen(); + auto g2 = std::move(g); + (void) g.begin(); +} + +void test_end_iterator_dereference() { + auto g = gen(); + auto i = g.begin(); + (void) *i; +} + +void test_end_iterator_incrementation() { + auto g = gen(); + auto i = g.begin(); + ++i; +} + +int main(int argc, char** argv) { + std_testing::death_test_executive exec; + + exec.add_death_tests({ + test_begin_after_initial_suspend_point, + test_begin_after_moving_from, + test_end_iterator_dereference, + test_end_iterator_incrementation, + }); + + return exec.run(argc, argv); +} diff --git a/tests/std/tests/P2502R2_generator_iterator/env.lst b/tests/std/tests/P2502R2_generator_iterator/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2502R2_generator_iterator/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2502R2_generator_iterator/test.cpp b/tests/std/tests/P2502R2_generator_iterator/test.cpp new file mode 100644 index 00000000000..aa7fd415d87 --- /dev/null +++ b/tests/std/tests/P2502R2_generator_iterator/test.cpp @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_generator_support.hpp" + +using namespace std; + +template +generator generate_zero() { + co_return; +} + +template > + requires default_initializable + && (same_as, ValueType> || constructible_from, ValueType&>) +generator generate_one() { + if constexpr (same_as, ValueType>) { + // non-proxy reference case + if constexpr (is_reference_v) { + remove_reference_t val{}; + co_yield static_cast(val); + } else { + co_yield ValueType{}; + } + } else { + ValueType val{}; + // proxy reference case + if constexpr (is_reference_v) { + // yielding a non-prvalue proxy reference is super weird, but not forbidden + remove_reference_t ref{val}; + co_yield static_cast(ref); + } else { + co_yield Ref{val}; + } + } +} + +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 +template +generator generate_one_recursively() { + co_yield ranges::elements_of{generate_zero()}; + co_yield ranges::elements_of{generate_one()}; + co_yield ranges::elements_of{generate_zero()}; +} +#endif // ^^^ no workaround ^^^ + +template +void test_one() { + using Gen = generator; + using Iter = ranges::iterator_t; + static_assert(input_iterator); + static_assert(sizeof(Iter) == sizeof(void*)); // NB: implementation defined + + // Test member types + static_assert(same_as>); + static_assert(same_as); + + // Test copying functions + static_assert(!is_copy_constructible_v); + static_assert(!is_copy_assignable_v); + + { // Test move constructor + Gen g = generate_zero(); + Iter i = g.begin(); + Iter j = move(i); + assert(j == default_sentinel); + + static_assert(is_nothrow_move_constructible_v); + } + + { // Test move assignment operator + Gen g1 = generate_one(); + Iter i = g1.begin(); + Gen g2 = generate_zero(); + Iter j = g2.begin(); + + same_as decltype(auto) k = (i = move(j)); + assert(&k == &i); + assert(k == default_sentinel); + static_assert(is_nothrow_move_assignable_v); + } + + { // Test indirection + auto g = generate_one(); + auto i = g.begin(); + + same_as> decltype(auto) r = *i; + + using ValueType = gen_value_t; + if constexpr (default_initializable && equality_comparable) { + assert(r == ValueType{}); + } + } + +#if !(defined(__clang__) && defined(_M_IX86)) // TRANSITION, LLVM-56507 + { // Test pre-incrementation + auto g = generate_one_recursively(); + auto i = g.begin(); + + same_as decltype(auto) i_ref = ++i; + assert(&i_ref == &i); + assert(i_ref == default_sentinel); + } + + { // Test post-incrementation + auto g = generate_one_recursively(); + auto i = g.begin(); + i++; + assert(i == default_sentinel); + + static_assert(is_void_v); + } +#endif // ^^^ no workaround ^^^ + + { // Test equal operator + auto g1 = generate_one(); + auto i = g1.begin(); + auto g2 = generate_zero(); + auto j = g2.begin(); + + same_as decltype(auto) b1 = i == default_sentinel; + assert(!b1); + + same_as decltype(auto) b2 = default_sentinel == j; + assert(b2); + + same_as decltype(auto) b3 = i != default_sentinel; + assert(b3); + + same_as decltype(auto) b4 = default_sentinel != j; + assert(!b4); + } +} + +template +void test_with_allocator() { + test_one(); + test_one>(); + test_one>(); + test_one>(); +} + +template +void test_with_type() { + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + + test_with_allocator, T>(); + test_with_allocator&, T>(); + test_with_allocator&, T>(); + test_with_allocator&&, T>(); + test_with_allocator&&, T>(); +} + +int main() { + test_with_type(); + test_with_type(); + test_with_type(); + test_with_type(); +} diff --git a/tests/std/tests/P2502R2_generator_promise/env.lst b/tests/std/tests/P2502R2_generator_promise/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2502R2_generator_promise/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2502R2_generator_promise/test.cpp b/tests/std/tests/P2502R2_generator_promise/test.cpp new file mode 100644 index 00000000000..42a2f7c09ea --- /dev/null +++ b/tests/std/tests/P2502R2_generator_promise/test.cpp @@ -0,0 +1,241 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "range_algorithm_support.hpp" +#include "test_generator_support.hpp" + +using namespace std; + +#pragma warning(disable : 28251) // Inconsistent annotation for 'new[]': this instance has no annotations. + +void* operator new[](size_t) { + abort(); +} + +void operator delete[](void*) noexcept { + abort(); +} + +template +concept HasOperatorNew = requires(Args&&... args) { + { Promise::operator new(forward(args)...) } -> same_as; +}; + +template +struct generator_allocator {}; + +template +struct generator_allocator> { + using type = Alloc; +}; + +template + requires convertible_to, typename Gen::yielded> +void test_yield_elements_of_range(typename Gen::promise_type& p) { + using Alloc = generator_allocator::type; + + { + using Awaitable = decltype(p.yield_value(ranges::elements_of{declval()})); + static_assert(same_as().await_ready()), bool>); + } + + if constexpr (!is_void_v) { + using Awaitable = decltype(p.yield_value(ranges::elements_of{declval(), declval()})); + static_assert(same_as().await_ready()), bool>); + } +} + +template +void test_operator_new(typename Gen::promise_type& p, const Alloc2& alloc2 = {}) { + using Promise = Gen::promise_type; + using Alloc = generator_allocator::type; + + // Test 'operator new(size_t)' + constexpr bool has_op_new1 = HasOperatorNew; + static_assert(has_op_new1 == (same_as || default_initializable) ); + if constexpr (has_op_new1) { + const size_t size = __STDCPP_DEFAULT_NEW_ALIGNMENT__; + void* const mem = p.operator new(size); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); + p.operator delete(mem, size); + } + + // Test 'operator new(size_t, allocator_arg_t, const Alloc2&, const Args&...)' + constexpr bool has_op_new2 = HasOperatorNew; + static_assert(has_op_new2 == (same_as || convertible_to) ); + if constexpr (has_op_new2) { + const size_t size = __STDCPP_DEFAULT_NEW_ALIGNMENT__; + void* const mem = p.operator new(size, allocator_arg, alloc2, 0, 0); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); + p.operator delete(mem, size); + } + + // Test 'operator new(size_t, const This&, allocator_arg_t, const Alloc2&, const Args&...)' + struct S {}; + constexpr bool has_op_new3 = HasOperatorNew; + static_assert(has_op_new3 == (same_as || convertible_to) ); + if constexpr (has_op_new3) { + const size_t size = __STDCPP_DEFAULT_NEW_ALIGNMENT__; + const S s; + void* const mem = p.operator new(size, s, allocator_arg, alloc2, 0, 0); + assert(reinterpret_cast(mem) % __STDCPP_DEFAULT_NEW_ALIGNMENT__ == 0); + p.operator delete(mem, size); + } +} + +template +void test_one() { + using Gen = generator; + using Promise = Gen::promise_type; + using Yielded = Gen::yielded; + static_assert(semiregular); + + Promise p; + + // Test that operator& for promise_type resolves to the built-in version and doesn't involve ADL + static_assert(same_as); + assert(&p == addressof(p)); + + // Test 'get_return_object' + static_assert(same_as); + static_assert(noexcept(p.get_return_object())); + + // Test 'initial_suspend' + static_assert(same_as); + static_assert(same_as); + static_assert(noexcept(p.initial_suspend())); + static_assert(noexcept(as_const(p).initial_suspend())); + + // Test 'final_suspend' + using FinalAwaitable = decltype(p.final_suspend()); + static_assert(convertible_to().await_ready()), bool>); + static_assert(noexcept(p.final_suspend())); + + // Test 'yield_value(yielded)' + static_assert(same_as())), suspend_always>); + static_assert(noexcept(p.yield_value(declval()))); + + // Test 'yield_value(const remove_reference_t&)' + if constexpr (is_rvalue_reference_v + && constructible_from, const remove_reference_t&>) { + using Lval = const remove_reference_t&; + using Awaitable = decltype(p.yield_value(declval())); + + static_assert(convertible_to().await_ready()), bool>); + static_assert(is_void_v().await_resume())>); + static_assert(noexcept(p.yield_value(declval())) + == is_nothrow_constructible_v, Lval>); // strengthened + } + + { // Test 'yield_value(elements_of)' + { + using Awaitable = decltype(p.yield_value(ranges::elements_of{declval()})); + static_assert(convertible_to().await_ready()), bool>); + } + + if constexpr (!is_void_v) { + using Awaitable = decltype(p.yield_value(ranges::elements_of{declval(), declval()})); + static_assert(convertible_to().await_ready()), bool>); + } + } + + using ValTy = conditional_t, remove_cvref_t, V>; + if constexpr (!TestingIncomplete && convertible_to) { + // Test 'yield_value(ranges::elements_of)' + test_yield_elements_of_range>(p); + test_yield_elements_of_range>(p); + test_yield_elements_of_range>(p); + test_yield_elements_of_range>(p); + } + + // Test 'await_transform' + static_assert(!requires(Promise& p) { p.await_transform(); }); + + // Test 'return_void' + static_assert(is_void_v); + static_assert(is_void_v); + static_assert(noexcept(p.return_void())); + static_assert(noexcept(as_const(p).return_void())); + + // Test 'unhandled_exception' + static_assert(is_void_v); + + // Test 'operator new(size_t, ARGS...)' + test_operator_new>(p); + test_operator_new>(p); + test_operator_new>(p); + if constexpr (same_as) { + test_operator_new>(p); + test_operator_new>(p); + test_operator_new>(p); + } + + // Non-portable size check + static_assert(sizeof(Promise) == 2 * sizeof(void*)); +} + +template +void test_with_allocator() { + test_one(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); + test_one, TestingIncomplete>(); +} + +template +void test_with_type() { + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + test_with_allocator(); + + test_with_allocator, T, TestingIncomplete>(); + test_with_allocator&, T, TestingIncomplete>(); + test_with_allocator&, T, TestingIncomplete>(); + test_with_allocator&&, T, TestingIncomplete>(); + test_with_allocator&&, T, TestingIncomplete>(); +} + +#ifndef _M_CEE // TRANSITION, VSO-1659496 +template +struct Holder { + T t; +}; + +struct Incomplete; +#endif // ^^^ no workaround ^^^ + +int main() { + test_with_type(); + test_with_type(); + test_with_type(); + test_with_type(); + test_with_type(); + test_with_allocator::reference, bool>(); +#ifndef _M_CEE // TRANSITION, VSO-1659496 + test_with_type*, true>(); +#endif // ^^^ no workaround ^^^ +} diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 9b56190a75f..028d8d68a3a 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -461,6 +461,12 @@ STATIC_ASSERT(__cpp_lib_gcd_lcm == 201606L); #error __cpp_lib_gcd_lcm is defined #endif +#if _HAS_CXX23 +STATIC_ASSERT(__cpp_lib_generator == 202207L); +#elif defined(__cpp_lib_generator) +#error __cpp_lib_generator is defined +#endif + STATIC_ASSERT(__cpp_lib_generic_associative_lookup == 201304L); #if _HAS_CXX20 diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index 2dfba425980..b642cb11c6c 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -31,6 +31,7 @@ PM_CL="/DMEOW_HEADER=forward_list" PM_CL="/DMEOW_HEADER=fstream" PM_CL="/DMEOW_HEADER=functional" PM_CL="/DMEOW_HEADER=future" +PM_CL="/DMEOW_HEADER=generator" PM_CL="/DMEOW_HEADER=initializer_list" PM_CL="/DMEOW_HEADER=iomanip" PM_CL="/DMEOW_HEADER=ios"