diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 303ae13a569..06727dd1fca 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -83,6 +83,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 44170ded701..44a5c5d50ba 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/__msvc_string_view.hpp b/stl/inc/__msvc_string_view.hpp index a0f6055a5c5..d291438e555 100644 --- a/stl/inc/__msvc_string_view.hpp +++ b/stl/inc/__msvc_string_view.hpp @@ -331,6 +331,17 @@ template <> struct char_traits : _WChar_traits {}; #endif // defined(_CRTBLD) +// signed char and other unsigned integral types are supported as an extension. +// Use of other arithmetic types and nullptr_t should be rejected. +template +constexpr bool _Is_implementation_handled_char_like_type = is_arithmetic_v<_Ty> || is_null_pointer_v<_Ty>; + +template +constexpr bool _Is_implementation_handled_char_traits = false; +template +constexpr bool _Is_implementation_handled_char_traits> = + _Is_implementation_handled_char_like_type<_Elem>; + #if defined(__cpp_char8_t) && !defined(__clang__) && !defined(__EDG__) #define _HAS_U8_INTRINSICS 1 #else // ^^^ Use intrinsics for char8_t / don't use said intrinsics vvv @@ -675,9 +686,9 @@ class _String_bitmap { // _String_bitmap for character types template class _String_bitmap<_Elem, false> { // _String_bitmap for wchar_t/unsigned short/char16_t/char32_t/etc. types public: - static_assert(is_unsigned_v<_Elem>, - "Standard char_traits is only provided for char, wchar_t, char16_t, and char32_t. See N4950 [char.traits]. " - "Visual C++ accepts other unsigned integral types as an extension."); + static_assert(is_unsigned_v<_Elem>, "Standard char_traits is only provided for char, wchar_t, char8_t, char16_t, " + "and char32_t. See N4988 [char.traits]. " + "Visual C++ accepts other unsigned integral types as an extension."); constexpr bool _Mark(const _Elem* _First, const _Elem* const _Last) noexcept { // mark this bitmap such that the characters in [_First, _Last) are intended to match @@ -702,7 +713,7 @@ class _String_bitmap<_Elem, false> { // _String_bitmap for wchar_t/unsigned shor bool _Matches[256] = {}; }; -template > +template > constexpr size_t _Traits_find_first_of(_In_reads_(_Hay_size) const _Traits_ptr_t<_Traits> _Haystack, const size_t _Hay_size, const size_t _Start_at, _In_reads_(_Needle_size) const _Traits_ptr_t<_Traits> _Needle, const size_t _Needle_size) noexcept { @@ -764,7 +775,7 @@ constexpr size_t _Traits_find_first_of(_In_reads_(_Hay_size) const _Traits_ptr_t return static_cast(-1); // no match } -template > +template > constexpr size_t _Traits_find_last_of(_In_reads_(_Hay_size) const _Traits_ptr_t<_Traits> _Haystack, const size_t _Hay_size, const size_t _Start_at, _In_reads_(_Needle_size) const _Traits_ptr_t<_Traits> _Needle, const size_t _Needle_size) noexcept { @@ -802,7 +813,7 @@ constexpr size_t _Traits_find_last_of(_In_reads_(_Hay_size) const _Traits_ptr_t< return static_cast(-1); // no match } -template > +template > constexpr size_t _Traits_find_first_not_of(_In_reads_(_Hay_size) const _Traits_ptr_t<_Traits> _Haystack, const size_t _Hay_size, const size_t _Start_at, _In_reads_(_Needle_size) const _Traits_ptr_t<_Traits> _Needle, const size_t _Needle_size) noexcept { @@ -851,7 +862,7 @@ constexpr size_t _Traits_find_not_ch(_In_reads_(_Hay_size) const _Traits_ptr_t<_ return static_cast(-1); // no match } -template > +template > constexpr size_t _Traits_find_last_not_of(_In_reads_(_Hay_size) const _Traits_ptr_t<_Traits> _Haystack, const size_t _Hay_size, const size_t _Start_at, _In_reads_(_Needle_size) const _Traits_ptr_t<_Traits> _Needle, const size_t _Needle_size) noexcept { 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/deque b/stl/inc/deque index 457136014d4..7d910fe2fde 100644 --- a/stl/inc/deque +++ b/stl/inc/deque @@ -997,7 +997,8 @@ public: const auto _Mask = static_cast(_Mapsize() - 1); - const auto _First_used_block_idx = static_cast(_Myoff() / _Block_size); + const auto _Unmasked_first_used_block_idx = static_cast(_Myoff() / _Block_size); + const auto _First_used_block_idx = static_cast(_Unmasked_first_used_block_idx & _Mask); // (_Myoff() + _Mysize() - 1) is for the last element, i.e. the back() of the deque. // Divide by _Block_size to get the unmasked index of the last used block. @@ -1017,7 +1018,8 @@ public: } } - const auto _Used_block_count = static_cast(_Unmasked_first_unused_block_idx - _First_used_block_idx); + const auto _Used_block_count = + static_cast(_Unmasked_first_unused_block_idx - _Unmasked_first_used_block_idx); size_type _New_block_count = _Minimum_map_size; // should be power of 2 @@ -1160,7 +1162,7 @@ private: public: template - decltype(auto) emplace_front(_Valty&&... _Val) { + _CONTAINER_EMPLACE_RETURN emplace_front(_Valty&&... _Val) { _Orphan_all(); _Emplace_front_internal(_STD forward<_Valty>(_Val)...); #if _HAS_CXX17 @@ -1169,7 +1171,7 @@ public: } template - decltype(auto) emplace_back(_Valty&&... _Val) { + _CONTAINER_EMPLACE_RETURN emplace_back(_Valty&&... _Val) { _Orphan_all(); _Emplace_back_internal(_STD forward<_Valty>(_Val)...); diff --git a/stl/inc/forward_list b/stl/inc/forward_list index f55da31fd95..62c888e47ba 100644 --- a/stl/inc/forward_list +++ b/stl/inc/forward_list @@ -700,7 +700,7 @@ public: } template - decltype(auto) emplace_front(_Valty&&... _Val) { // insert element at beginning + _CONTAINER_EMPLACE_RETURN emplace_front(_Valty&&... _Val) { // insert element at beginning _Insert_after(_Mypair._Myval2._Before_head(), _STD forward<_Valty>(_Val)...); #if _HAS_CXX17 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 747ea235870..b5876a0d885 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -77,6 +77,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/list b/stl/inc/list index 5d42e9fe73d..033a43c60c7 100644 --- a/stl/inc/list +++ b/stl/inc/list @@ -981,7 +981,7 @@ public: } template - decltype(auto) emplace_front(_Valty&&... _Val) { // insert element at beginning + _CONTAINER_EMPLACE_RETURN emplace_front(_Valty&&... _Val) { // insert element at beginning reference _Result = _Emplace(_Mypair._Myval2._Myhead->_Next, _STD forward<_Valty>(_Val)...)->_Myval; #if _HAS_CXX17 @@ -992,7 +992,7 @@ public: } template - decltype(auto) emplace_back(_Valty&&... _Val) { // insert element at end + _CONTAINER_EMPLACE_RETURN emplace_back(_Valty&&... _Val) { // insert element at end reference _Result = _Emplace(_Mypair._Myval2._Myhead, _STD forward<_Valty>(_Val)...)->_Myval; #if _HAS_CXX17 diff --git a/stl/inc/optional b/stl/inc/optional index 6ce1bcf4c31..b6d4bcc69f2 100644 --- a/stl/inc/optional +++ b/stl/inc/optional @@ -214,6 +214,16 @@ class optional : private _SMF_control<_Optional_construct_base<_Ty>, _Ty> { private: using _Mybase = _SMF_control<_Optional_construct_base<_Ty>, _Ty>; + template + friend class optional; + +#if _HAS_CXX23 + template + constexpr optional(_Construct_from_invoke_result_tag _Tag, _Fn&& _Func, _Ux&& _Arg) + noexcept(noexcept(static_cast<_Ty>(_STD invoke(_STD forward<_Fn>(_Func), _STD forward<_Ux>(_Arg))))) + : _Mybase(_Tag, _STD forward<_Fn>(_Func), _STD forward<_Ux>(_Arg)) {} +#endif // _HAS_CXX23 + public: static_assert(!_Is_any_of_v, nullopt_t, in_place_t>, "T in optional must be a type other than nullopt_t or in_place_t (N4950 [optional.optional.general]/3)."); @@ -273,13 +283,6 @@ public: } } -#if _HAS_CXX23 - template - constexpr optional(_Construct_from_invoke_result_tag _Tag, _Fn&& _Func, _Ux&& _Arg) - noexcept(noexcept(static_cast<_Ty>(_STD invoke(_STD forward<_Fn>(_Func), _STD forward<_Ux>(_Arg))))) - : _Mybase(_Tag, _STD forward<_Fn>(_Func), _STD forward<_Ux>(_Arg)) {} -#endif // _HAS_CXX23 - _CONSTEXPR20 optional& operator=(nullopt_t) noexcept { reset(); return *this; diff --git a/stl/inc/queue b/stl/inc/queue index 2d8b6bd0df1..9c0262663c8 100644 --- a/stl/inc/queue +++ b/stl/inc/queue @@ -135,7 +135,7 @@ public: #endif // _HAS_CXX23 template - decltype(auto) emplace(_Valty&&... _Val) { + _CONTAINER_EMPLACE_RETURN emplace(_Valty&&... _Val) { #if _HAS_CXX17 return c.emplace_back(_STD forward<_Valty>(_Val)...); #else // ^^^ _HAS_CXX17 / !_HAS_CXX17 vvv diff --git a/stl/inc/regex b/stl/inc/regex index 06cf594662d..3af7e36418d 100644 --- a/stl/inc/regex +++ b/stl/inc/regex @@ -193,19 +193,29 @@ struct _Char_traits_lt { } }; +// signed char and other unsigned integral types are supported as an extension. +template +constexpr bool _Is_predefined_char_like_type = _Is_character<_Ty>::value || is_unsigned_v<_Ty>; + // library-provided char_traits::eq behaves like equal_to<_Elem> -// TRANSITION: This should not be activated for user-defined specializations of char_traits template constexpr bool _Can_memcmp_elements_with_pred<_Elem, _Elem, _Char_traits_eq>> = - _Can_memcmp_elements<_Elem, _Elem>; + _Is_predefined_char_like_type<_Elem> && _Can_memcmp_elements<_Elem, _Elem>; -// library-provided char_traits::lt behaves like less> -// TRANSITION: This should not be activated for user-defined specializations of char_traits -template -struct _Lex_compare_memcmp_classify_pred<_Elem, _Elem, _Char_traits_lt>> { +template > +struct _Lex_compare_memcmp_classify_pred_for_char_traits_lt { using _UElem = make_unsigned_t<_Elem>; using _Pred = conditional_t<_Lex_compare_memcmp_classify_elements<_UElem, _UElem>, less, void>; }; +template +struct _Lex_compare_memcmp_classify_pred_for_char_traits_lt<_Elem, false> { + using _Pred = void; +}; + +// library-provided char_traits::lt behaves like less> +template +struct _Lex_compare_memcmp_classify_pred<_Elem, _Elem, _Char_traits_lt>> + : _Lex_compare_memcmp_classify_pred_for_char_traits_lt<_Elem> {}; template struct _Cmp_cs { // functor to compare two character values for equality diff --git a/stl/inc/stack b/stl/inc/stack index 02c404594c1..2640586df5a 100644 --- a/stl/inc/stack +++ b/stl/inc/stack @@ -120,7 +120,7 @@ public: #endif // _HAS_CXX23 template - decltype(auto) emplace(_Valty&&... _Val) { + _CONTAINER_EMPLACE_RETURN emplace(_Valty&&... _Val) { #if _HAS_CXX17 return c.emplace_back(_STD forward<_Valty>(_Val)...); #else // ^^^ _HAS_CXX17 / !_HAS_CXX17 vvv diff --git a/stl/inc/vector b/stl/inc/vector index 8a123af1f31..294d2ea69fa 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -853,7 +853,7 @@ private: public: template - _CONSTEXPR20 decltype(auto) emplace_back(_Valty&&... _Val) { + _CONSTEXPR20 _CONTAINER_EMPLACE_RETURN emplace_back(_Valty&&... _Val) { // insert by perfectly forwarding into element at end, provide strong guarantee _Ty& _Result = _Emplace_one_at_back(_STD forward<_Valty>(_Val)...); #if _HAS_CXX17 @@ -2979,7 +2979,7 @@ public: } template - _CONSTEXPR20 decltype(auto) emplace_back(_Valty&&... _Val) { + _CONSTEXPR20 _CONTAINER_EMPLACE_RETURN emplace_back(_Valty&&... _Val) { bool _Tmp(_STD forward<_Valty>(_Val)...); push_back(_Tmp); @@ -3922,6 +3922,18 @@ _CONSTEXPR20 _OutIt _Copy_vbool(_VbIt _First, _VbIt _Last, _OutIt _Dest) { #undef _ASAN_VECTOR_EXTEND_GUARD #undef _ASAN_VECTOR_RELEASE_GUARD #undef _INSERT_VECTOR_ANNOTATION + +#if _HAS_CXX23 +_EXPORT_STD struct sorted_unique_t { + explicit sorted_unique_t() = default; +}; +_EXPORT_STD inline constexpr sorted_unique_t sorted_unique{}; + +_EXPORT_STD struct sorted_equivalent_t { + explicit sorted_equivalent_t() = default; +}; +_EXPORT_STD inline constexpr sorted_equivalent_t sorted_equivalent{}; +#endif // ^^^ _HAS_CXX23 ^^^ _STD_END #pragma pop_macro("new") diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 06358ef270b..3aa11f488c8 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 @@ -2619,20 +2641,14 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot } while (_UVal_trunc != 0); return _RNext; } - -#if _HAS_CXX23 -_EXPORT_STD struct sorted_unique_t { - explicit sorted_unique_t() = default; -}; -_EXPORT_STD inline constexpr sorted_unique_t sorted_unique{}; - -_EXPORT_STD struct sorted_equivalent_t { - explicit sorted_equivalent_t() = default; -}; -_EXPORT_STD inline constexpr sorted_equivalent_t sorted_equivalent{}; -#endif // ^^^ _HAS_CXX23 ^^^ _STD_END +#if _HAS_CXX17 +#define _CONTAINER_EMPLACE_RETURN reference +#else // ^^^ _HAS_CXX17 ^^^ / vvv !_HAS_CXX17 vvv +#define _CONTAINER_EMPLACE_RETURN void +#endif // ^^^ !_HAS_CXX17 ^^^ + #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS #pragma warning(pop) diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index d9076fb0932..6c76238f906 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -381,6 +381,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 @@ -393,6 +394,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 @@ -1768,6 +1770,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 3acb3748c4f..16fa73112be 100644 --- a/stl/modules/std.ixx +++ b/stl/modules/std.ixx @@ -73,6 +73,9 @@ export module std; #include #include #include +#if _HAS_CXX23 +#include +#endif // _HAS_CXX23 #include #include #include diff --git a/stl/msbuild/stl_1/xmd/dirs.proj b/stl/msbuild/stl_1/xmd/dirs.proj index f9b3be319d5..2389765745e 100644 --- a/stl/msbuild/stl_1/xmd/dirs.proj +++ b/stl/msbuild/stl_1/xmd/dirs.proj @@ -6,9 +6,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - + diff --git a/stl/msbuild/stl_2/xmd/dirs.proj b/stl/msbuild/stl_2/xmd/dirs.proj index 32c9b10fc32..c8f8062d269 100644 --- a/stl/msbuild/stl_2/xmd/dirs.proj +++ b/stl/msbuild/stl_2/xmd/dirs.proj @@ -6,9 +6,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - + diff --git a/stl/msbuild/stl_atomic_wait/xmd/dirs.proj b/stl/msbuild/stl_atomic_wait/xmd/dirs.proj index 2e5c24526f3..e0506689c99 100644 --- a/stl/msbuild/stl_atomic_wait/xmd/dirs.proj +++ b/stl/msbuild/stl_atomic_wait/xmd/dirs.proj @@ -6,9 +6,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - + diff --git a/stl/msbuild/stl_base/dirs.proj b/stl/msbuild/stl_base/dirs.proj index a46ac76b51f..5bfe0aa94f1 100644 --- a/stl/msbuild/stl_base/dirs.proj +++ b/stl/msbuild/stl_base/dirs.proj @@ -12,8 +12,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - - + + diff --git a/stl/msbuild/stl_base/xmd/dirs.proj b/stl/msbuild/stl_base/xmd/dirs.proj index b1948b2b1ff..1f5c0a0ba77 100644 --- a/stl/msbuild/stl_base/xmd/dirs.proj +++ b/stl/msbuild/stl_base/xmd/dirs.proj @@ -6,10 +6,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - - + diff --git a/stl/msbuild/stl_base/xmt/dirs.proj b/stl/msbuild/stl_base/xmt/dirs.proj index 336c1a96edb..b59f092fe5b 100644 --- a/stl/msbuild/stl_base/xmt/dirs.proj +++ b/stl/msbuild/stl_base/xmt/dirs.proj @@ -7,11 +7,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - - + + diff --git a/stl/msbuild/stl_codecvt_ids/xmd/dirs.proj b/stl/msbuild/stl_codecvt_ids/xmd/dirs.proj index f87e96750c8..77dc722bdaa 100644 --- a/stl/msbuild/stl_codecvt_ids/xmd/dirs.proj +++ b/stl/msbuild/stl_codecvt_ids/xmd/dirs.proj @@ -6,9 +6,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - + diff --git a/stl/msbuild/stl_post/xmd/dirs.proj b/stl/msbuild/stl_post/xmd/dirs.proj index 40f86dd45ae..5b8316f58b7 100644 --- a/stl/msbuild/stl_post/xmd/dirs.proj +++ b/stl/msbuild/stl_post/xmd/dirs.proj @@ -6,9 +6,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - + diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 1303fc87102..a9bab3c4f52 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -1225,9 +1225,6 @@ std/numerics/c.math/hermite.pass.cpp FAIL # Not analyzed. Test coverage for LLVM-104496 uses span. std/containers/views/views.span/span.cons/copy.pass.cpp FAIL -# Not analyzed. LLVM-103409 added a test to verify that std::optional's internal constructors aren't visible to users. -std/utilities/optional/optional.object/optional.object.ctor/gh_101960_internal_ctor.compile.pass.cpp FAIL - # *** XFAILS WHICH PASS *** # These tests contain `// XFAIL: msvc` comments, which accurately describe runtime failures for x86 and x64. 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 7ad2af81eb9..df2dd08bb9f 100644 --- a/tests/std/include/test_header_units_and_modules.hpp +++ b/tests/std/include/test_header_units_and_modules.hpp @@ -317,6 +317,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 ."); @@ -1219,6 +1233,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 64270cd47f8..f8f9c8ed32f 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -250,6 +250,7 @@ tests\GH_004618_mixed_operator_usage_keeps_statistical_properties tests\GH_004618_normal_distribution_avoids_resets tests\GH_004657_expected_constraints_permissive tests\GH_004845_logical_operator_traits_with_non_bool_constant +tests\GH_004930_char_traits_user_specialization tests\LWG2381_num_get_floating_point tests\LWG2597_complex_branch_cut tests\LWG3018_shared_ptr_function @@ -672,6 +673,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 @@ -768,3 +773,4 @@ tests\VSO_0971246_legacy_await_headers tests\VSO_1775715_user_defined_modules tests\VSO_1804139_static_analysis_warning_with_single_element_array tests\VSO_1925201_iter_traits +tests\VSO_2252142_wrong_C5046 diff --git a/tests/std/tests/GH_002769_handle_deque_block_pointers/test.cpp b/tests/std/tests/GH_002769_handle_deque_block_pointers/test.cpp index 1d71a44c9e1..bdf2f345c98 100644 --- a/tests/std/tests/GH_002769_handle_deque_block_pointers/test.cpp +++ b/tests/std/tests/GH_002769_handle_deque_block_pointers/test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include using namespace std; @@ -328,8 +329,31 @@ void test_inconsistent_difference_types() { assert(counter == 0); } +// Also test GH-4954: Endless loop in deque::shrink_to_fit() +void test_gh_4954() { + deque qu; + mt19937_64 mteng; + + for (int i = 0; i < 256; ++i) { + const auto push_count = static_cast((mteng() & 32767U) + 1); + for (size_t j = 0; j < push_count; ++j) { + qu.push_back(0); + } + + auto pop_count = static_cast((mteng() & 32767U) + 1); + if (i % 100 == 0 || pop_count > qu.size()) { + pop_count = qu.size(); + } + for (size_t j = 0; j < pop_count; ++j) { + qu.pop_front(); + } + qu.shrink_to_fit(); + } +} + int main() { test_gh_2769(); test_gh_3717(); + test_gh_4954(); test_inconsistent_difference_types(); } diff --git a/tests/std/tests/GH_004930_char_traits_user_specialization/env.lst b/tests/std/tests/GH_004930_char_traits_user_specialization/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_004930_char_traits_user_specialization/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/GH_004930_char_traits_user_specialization/test.cpp b/tests/std/tests/GH_004930_char_traits_user_specialization/test.cpp new file mode 100644 index 00000000000..a67664a830d --- /dev/null +++ b/tests/std/tests/GH_004930_char_traits_user_specialization/test.cpp @@ -0,0 +1,285 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAS_CXX17 +#include +#endif // _HAS_CXX17 + +#if _HAS_CXX20 +#define CONSTEXPR20 constexpr +#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv +#define CONSTEXPR20 inline +#endif // ^^^ !_HAS_CXX20 ^^^ + +using namespace std; + +enum odd_char : unsigned char {}; + +template <> +class std::char_traits { +private: + static constexpr unsigned char odd_mask = 0xF; + +public: + using char_type = odd_char; + using int_type = int; + using off_type = streamoff; + using pos_type = streampos; + using state_type = mbstate_t; + + static constexpr bool eq(const char_type c, const char_type d) noexcept { + return ((static_cast(c) ^ static_cast(d)) & odd_mask) == 0; + } + + static constexpr bool lt(const char_type c, const char_type d) noexcept { + return (static_cast(c) & odd_mask) < (static_cast(d) & odd_mask); + } + + static constexpr int compare(const char_type* const c, const char_type* const d, const size_t n) noexcept { + for (size_t i = 0; i != n; ++i) { + int ci = static_cast(c[i]) & odd_mask; + int di = static_cast(d[i]) & odd_mask; + int r = ci - di; + if (r != 0) { + return r; + } + } + + return 0; + } + + static constexpr size_t length(const char_type* const p) noexcept { + const char_type* c = p; + while ((static_cast(*c) & odd_mask) != 0) { + ++c; + } + + return static_cast(c - p); + } + + static constexpr const char_type* find(const char_type* const p, const size_t n, const char_type c) noexcept { + for (size_t i = 0; i != n; ++i) { + if (eq(p[i], c)) { + return p + i; + } + } + + return nullptr; + } + + static CONSTEXPR20 char_type* move(char_type* const s, const char_type* const p, const size_t n) noexcept { +#if _HAS_CXX20 + if (is_constant_evaluated()) { + bool is_dst_in_src_range = false; + for (size_t i = 0; i != n; ++i) { + if (p + i == s) { + is_dst_in_src_range = true; + break; + } + } + + if (is_dst_in_src_range) { + for (size_t i = n; i != 0;) { + --i; + s[i] = p[i]; + } + } else { + for (size_t i = 0; i != n; ++i) { + s[i] = p[i]; + } + } + } else +#endif // _HAS_CXX20 + { + memmove(s, p, n); + } + return s; + } + + static CONSTEXPR20 char_type* copy(char_type* const s, const char_type* const p, const size_t n) noexcept { +#if _HAS_CXX20 + if (is_constant_evaluated()) { + for (size_t i = 0; i != n; ++i) { + s[i] = p[i]; + } + } else +#endif // _HAS_CXX20 + { + memmove(s, p, n); + } + return s; + } + + static constexpr void assign(char_type& r, const char_type& d) noexcept { + r = d; + } + static CONSTEXPR20 char_type* assign(char_type* const s, const size_t n, const char_type c) noexcept { +#if _HAS_CXX20 + if (is_constant_evaluated()) { + for (size_t i = 0; i != n; ++i) { + s[i] = c; + } + } else +#endif // _HAS_CXX20 + { + memset(s, static_cast(c), n); + } + return s; + } + + static constexpr bool not_eof(const int_type i) noexcept { + return i != -1; + } + + static constexpr char_type to_char_type(const int_type i) noexcept { + return static_cast(static_cast(i)); + } + + static constexpr int_type to_int_type(const char_type i) noexcept { + return static_cast(i); + } + + static constexpr bool eq_int_type(const int_type c, const int_type d) noexcept { + if (c == -1) { + return d == -1; + } else { + return ((c ^ d) & odd_mask) == 0; + } + } + + static constexpr int_type eof() noexcept { + return -1; + } +}; + +CONSTEXPR20 bool test_gh_4930() { + constexpr odd_char s_init[]{static_cast(0x55), static_cast(0x44), static_cast(0x33), + static_cast(0x22), static_cast(0x11), static_cast(0)}; + constexpr odd_char s2_init[]{static_cast(0x83), static_cast(0x12), static_cast(0)}; + +#if _HAS_CXX17 + using odd_string_view = basic_string_view; + + odd_string_view sv(s_init); + + assert(sv.length() == 5); + + assert(sv.find(static_cast(0x54)) == 1); + assert(sv.find(static_cast(0x26)) == sv.npos); + + assert(sv.rfind(static_cast(0x54)) == 1); + assert(sv.rfind(static_cast(0x26)) == sv.npos); + + odd_string_view sv2(s2_init); + + assert(sv.compare(sv2) > 0); +#if _HAS_CXX20 + assert(!sv.starts_with(sv2)); + assert(!sv.ends_with(sv2)); +#if _HAS_CXX23 + assert(sv.contains(sv2)); +#endif // _HAS_CXX23 +#endif // _HAS_CXX20 + + assert(sv.find(sv2) == 2); + + assert(sv.rfind(sv2) == 2); + + assert(sv.find_first_of(sv2) == 2); + assert(sv2.find_first_of(sv) == 0); + + assert(sv.find_last_of(sv2) == 3); + assert(sv2.find_last_of(sv) == 1); + + assert(sv.find_first_not_of(sv2) == 0); + assert(sv2.find_first_not_of(sv) == sv2.npos); + + assert(sv.find_last_not_of(sv2) == 4); + assert(sv2.find_last_not_of(sv) == sv2.npos); +#endif // _HAS_CXX17 + + using odd_string = basic_string; + + odd_string s(s_init); + + assert(s.length() == 5); + + assert(s.find(static_cast(0x54)) == 1); + assert(s.find(static_cast(0x26)) == s.npos); + + assert(s.rfind(static_cast(0x54)) == 1); + assert(s.rfind(static_cast(0x26)) == s.npos); + + odd_string s2(s2_init); + + assert(s.compare(s2) > 0); +#if _HAS_CXX20 + assert(!s.starts_with(s2)); + assert(!s.ends_with(s2)); +#if _HAS_CXX23 + assert(s.contains(s2)); +#endif // _HAS_CXX23 +#endif // _HAS_CXX20 + + assert(s.find(s2) == 2); + + assert(s.rfind(s2) == 2); + + assert(s.find_first_of(s2) == 2); + assert(s2.find_first_of(s) == 0); + + assert(s.find_last_of(s2) == 3); + assert(s2.find_last_of(s) == 1); + + assert(s.find_first_not_of(s2) == 0); + assert(s2.find_first_not_of(s) == s2.npos); + + assert(s.find_last_not_of(s2) == 4); + assert(s2.find_last_not_of(s) == s2.npos); + +#if _HAS_CXX17 + assert(s.compare(sv2) > 0); +#if _HAS_CXX20 + assert(!s.starts_with(sv2)); + assert(!s.ends_with(sv2)); +#if _HAS_CXX23 + assert(s.contains(sv2)); +#endif // _HAS_CXX23 +#endif // _HAS_CXX20 + + assert(s.find(sv2) == 2); + + assert(s.rfind(sv2) == 2); + + assert(s.find_first_of(sv2) == 2); + assert(s2.find_first_of(sv) == 0); + + assert(s.find_last_of(sv2) == 3); + assert(s2.find_last_of(sv) == 1); + + assert(s.find_first_not_of(sv2) == 0); + assert(s2.find_first_not_of(sv) == s2.npos); + + assert(s.find_last_not_of(sv2) == 4); + assert(s2.find_last_not_of(sv) == s2.npos); +#endif // _HAS_CXX17 + + return true; +} + +#if _HAS_CXX20 +static_assert(test_gh_4930()); +#endif // _HAS_CXX20 + +int main() { + assert(test_gh_4930()); +} 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 d62aa8ec375..38bb62470a6 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 @@ -30,6 +30,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 57d47dc088e..0b1a01af0d8 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -40,6 +40,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 24edabcdf53..c8165af7c46 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 @@ -473,6 +473,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/VSO_2252142_wrong_C5046/env.lst b/tests/std/tests/VSO_2252142_wrong_C5046/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/VSO_2252142_wrong_C5046/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/VSO_2252142_wrong_C5046/test.compile.pass.cpp b/tests/std/tests/VSO_2252142_wrong_C5046/test.compile.pass.cpp new file mode 100644 index 00000000000..8fa63feda4f --- /dev/null +++ b/tests/std/tests/VSO_2252142_wrong_C5046/test.compile.pass.cpp @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +// Test for DevCom-10745303 / VSO-2252142 "C5046 is wrongly triggered in unevaluated context" + +#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) + +using namespace std; + +template +struct convertible_to_any { + operator T() &&; // not defined, only used in unevaluated context +}; + +template +constexpr bool has_emplace = false; +template +constexpr bool has_emplace().emplace(declval>()))>> = true; + +template +constexpr bool has_emplace_back = false; +template +constexpr bool has_emplace_back().emplace_back(declval>()))>> = true; + +template +constexpr bool has_emplace_front = false; +template +constexpr bool has_emplace_front().emplace_front(declval>()))>> = true; + +namespace { + struct S2 {}; +} // namespace + +// Was emitting "warning C5046: Symbol involving type with internal linkage not defined" +// as a consequence of our use of return type deduction for the pertinent container functions. +STATIC_ASSERT(has_emplace_back>); +STATIC_ASSERT(has_emplace_back>); +STATIC_ASSERT(has_emplace_front>); +STATIC_ASSERT(has_emplace_front>); +STATIC_ASSERT(has_emplace_back>); +STATIC_ASSERT(has_emplace_front>); +STATIC_ASSERT(has_emplace>); +STATIC_ASSERT(has_emplace>); +STATIC_ASSERT(has_emplace_back>); // Cannot trigger this bug, but for consistency diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index 329696d45e5..67055bfb82e 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -33,6 +33,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"