diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 4e431bb53a0..e3e5307ec4c 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -94,10 +94,6 @@ struct _Optimistic_temporary_buffer { // temporary storage with _alloca-like att #ifdef __cpp_lib_concepts namespace ranges { - // CONCEPT _Convertible_from - template - concept _Convertible_from = convertible_to<_From, _To>; - #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-attributes" @@ -137,23 +133,6 @@ namespace ranges { } }; - // STRUCT TEMPLATE in_out_result - template - struct in_out_result { - [[no_unique_address]] _In in; - [[no_unique_address]] _Out out; - - template <_Convertible_from _IIn, _Convertible_from _OOut> - constexpr operator in_out_result<_IIn, _OOut>() const& { - return {in, out}; - } - - template <_Convertible_from<_In> _IIn, _Convertible_from<_Out> _OOut> - constexpr operator in_out_result<_IIn, _OOut>() && { - return {_STD move(in), _STD move(out)}; - } - }; - // STRUCT TEMPLATE in_in_out_result template struct in_in_out_result { diff --git a/stl/inc/memory b/stl/inc/memory index 67b1cc410d3..7f0bc401a32 100644 --- a/stl/inc/memory +++ b/stl/inc/memory @@ -26,6 +26,39 @@ _STL_DISABLE_CLANG_WARNINGS #undef new _STD_BEGIN +#ifdef __cpp_lib_concepts +namespace ranges { + // clang-format off + // CONCEPT _No_throw_input_iterator + template + concept _No_throw_input_iterator = input_iterator<_It> + && is_lvalue_reference_v> + && same_as>, iter_value_t<_It>>; + + // CONCEPT _No_throw_sentinel_for + template + concept _No_throw_sentinel_for = sentinel_for<_Se, _It>; + + // CONCEPT _No_throw_forward_iterator + template + concept _No_throw_forward_iterator = _No_throw_input_iterator<_It> + && forward_iterator<_It> + && _No_throw_sentinel_for<_It, _It>; + + // CONCEPT _No_throw_input_range + template + concept _No_throw_input_range = range<_Rng> + && _No_throw_input_iterator> + && _No_throw_sentinel_for, iterator_t<_Rng>>; + + // CONCEPT _No_throw_forward_range + template + concept _No_throw_forward_range = _No_throw_input_range<_Rng> + && _No_throw_forward_iterator>; + // clang-format on +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE uninitialized_copy_n #if _HAS_IF_CONSTEXPR template @@ -97,6 +130,71 @@ _NoThrowFwdIt uninitialized_move(const _InIt _First, const _InIt _Last, _NoThrow return _Dest; } +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE uninitialized_move_result + template + using uninitialized_move_result = in_out_result<_In, _Out>; + + // VARIABLE ranges::uninitialized_move + class _Uninitialized_move_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se1, _No_throw_forward_iterator _It2, _No_throw_sentinel_for<_It2> _Se2> + requires constructible_from, iter_rvalue_reference_t<_It1>> + uninitialized_move_result<_It1, _It2> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2) const { + // clang-format on + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UResult = + _Uninitialized_move_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)), + _Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2))); + + _Seek_wrapped(_First1, _STD move(_UResult.in)); + _Seek_wrapped(_First2, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_First2)}; + } + + // clang-format off + template + requires constructible_from, range_rvalue_reference_t<_Rng1>> + uninitialized_move_result, borrowed_iterator_t<_Rng2>> operator()( + _Rng1&& _Range1, _Rng2&& _Range2) const { + // clang-format on + auto _First1 = _RANGES begin(_Range1); + auto _UResult = _Uninitialized_move_unchecked( + _Get_unwrapped(_STD move(_First1)), _Uend(_Range1), _Ubegin(_Range2), _Uend(_Range2)); + + _Seek_wrapped(_First1, _STD move(_UResult.in)); + return {_STD move(_First1), _Rewrap_iterator(_Range2, _STD move(_UResult.out))}; + } + + private: + template + _NODISCARD static uninitialized_move_result<_It1, _It2> _Uninitialized_move_unchecked( + _It1 _IFirst, const _Se1 _ILast, _It2 _OFirst, const _Se2 _OLast) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(_No_throw_forward_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(_No_throw_sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT(constructible_from, iter_rvalue_reference_t<_It1>>); + + _Uninitialized_backout _Backout{_STD move(_OFirst)}; + + for (; _IFirst != _ILast && _Backout._Last != _OLast; ++_IFirst) { + _Backout._Emplace_back(_RANGES iter_move(_IFirst)); + } + + return {_STD move(_IFirst), _Backout._Release()}; + } + }; + + inline constexpr _Uninitialized_move_fn uninitialized_move{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE uninitialized_move_n template pair<_InIt, _NoThrowFwdIt> uninitialized_move_n(_InIt _First, const _Diff _Count_raw, _NoThrowFwdIt _Dest) { diff --git a/stl/inc/xmemory b/stl/inc/xmemory index abb94ba7ac0..c5744776e0c 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -263,9 +263,19 @@ _Pointer _Refancy(_Pointer _Ptr) noexcept { } // FUNCTION TEMPLATE _Destroy_in_place +template +/* _CONSTEXPR20_DYNALLOC */ void _Destroy_range(_NoThrowFwdIt _First, _NoThrowSentinel _Last) noexcept; + template -void _Destroy_in_place(_Ty& _Obj) noexcept { - _Obj.~_Ty(); +/* _CONSTEXPR20_DYNALLOC */ void _Destroy_in_place(_Ty& _Obj) noexcept { +#if _HAS_IF_CONSTEXPR + if constexpr (is_array_v<_Ty>) { + _Destroy_range(_Obj, _Obj + extent_v<_Ty>); + } else +#endif // _HAS_IF_CONSTEXPR + { + _Obj.~_Ty(); + } } // FUNCTION TEMPLATE _Const_cast @@ -948,7 +958,8 @@ void _Pocs(_Alloc& _Left, _Alloc& _Right) noexcept { // FUNCTION TEMPLATE _Destroy_range WITH ALLOC template -void _Destroy_range(_Alloc_ptr_t<_Alloc> _First, const _Alloc_ptr_t<_Alloc> _Last, _Alloc& _Al) noexcept { +/* _CONSTEXPR20_DYNALLOC */ void _Destroy_range( + _Alloc_ptr_t<_Alloc> _First, const _Alloc_ptr_t<_Alloc> _Last, _Alloc& _Al) noexcept { // note that this is an optimization for debug mode codegen; in release mode the BE removes all of this using _Ty = typename _Alloc::value_type; if _CONSTEXPR_IF (!conjunction_v, _Uses_default_destroy<_Alloc, _Ty*>>) { @@ -959,8 +970,8 @@ void _Destroy_range(_Alloc_ptr_t<_Alloc> _First, const _Alloc_ptr_t<_Alloc> _Las } // FUNCTION TEMPLATE _Destroy_range -template -void _Destroy_range(_NoThrowFwdIt _First, const _NoThrowFwdIt _Last) noexcept { +template +/* _CONSTEXPR20_DYNALLOC */ void _Destroy_range(_NoThrowFwdIt _First, const _NoThrowSentinel _Last) noexcept { // note that this is an optimization for debug mode codegen; in release mode the BE removes all of this if _CONSTEXPR_IF (!is_trivially_destructible_v<_Iter_value_t<_NoThrowFwdIt>>) { for (; _First != _Last; ++_First) { @@ -1414,29 +1425,64 @@ struct _Uninitialized_backout { // struct to undo partially constructed ranges i _NoThrowFwdIt _First; _NoThrowFwdIt _Last; - explicit _Uninitialized_backout(_NoThrowFwdIt _Dest) : _First(_Dest), _Last(_Dest) {} + constexpr explicit _Uninitialized_backout(_NoThrowFwdIt _Dest) : _First(_Dest), _Last(_Dest) {} - _Uninitialized_backout(_NoThrowFwdIt _First_, _NoThrowFwdIt _Last_) : _First(_First_), _Last(_Last_) {} + constexpr _Uninitialized_backout(_NoThrowFwdIt _First_, _NoThrowFwdIt _Last_) : _First(_First_), _Last(_Last_) {} _Uninitialized_backout(const _Uninitialized_backout&) = delete; _Uninitialized_backout& operator=(const _Uninitialized_backout&) = delete; - ~_Uninitialized_backout() { + /* _CONSTEXPR20_DYNALLOC */ ~_Uninitialized_backout() { _Destroy_range(_First, _Last); } template - void _Emplace_back(_Types&&... _Vals) { // construct a new element at *_Last and increment + /* _CONSTEXPR20_DYNALLOC */ void _Emplace_back(_Types&&... _Vals) { + // construct a new element at *_Last and increment _Construct_in_place(*_Last, _STD forward<_Types>(_Vals)...); ++_Last; } - _NoThrowFwdIt _Release() { // suppress any exception handling backout and return _Last + /* _CONSTEXPR20_DYNALLOC */ _NoThrowFwdIt _Release() { // suppress any exception handling backout and return _Last _First = _Last; return _Last; } }; +#ifdef __cpp_lib_concepts +namespace ranges { + // CONCEPT _Convertible_from + template + concept _Convertible_from = convertible_to<_From, _To>; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-attributes" +#endif // __clang__ + + // STRUCT TEMPLATE in_out_result + template + struct in_out_result { + [[no_unique_address]] _In in; + [[no_unique_address]] _Out out; + + template <_Convertible_from _IIn, _Convertible_from _OOut> + constexpr operator in_out_result<_IIn, _OOut>() const& { + return {in, out}; + } + + template <_Convertible_from<_In> _IIn, _Convertible_from<_Out> _OOut> + constexpr operator in_out_result<_IIn, _OOut>() && { + return {_STD move(in), _STD move(out)}; + } + }; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif // __clang__ +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE _Uninitialized_move_unchecked #if _HAS_IF_CONSTEXPR template diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index 0e766db6fda..3a0a27832f3 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -415,8 +415,7 @@ namespace test { ++ptr_; } - [[nodiscard]] constexpr friend std::remove_cv_t iter_move(iterator const& i) - requires at_least && std::constructible_from, Element> { + [[nodiscard]] constexpr friend Element&& iter_move(iterator const& i) requires at_least { return std::move(*i.ptr_); } diff --git a/tests/std/test.lst b/tests/std/test.lst index f6eda1e96ff..16a4927a181 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -297,6 +297,7 @@ tests\P0896R4_ranges_alg_shuffle tests\P0896R4_ranges_alg_swap_ranges tests\P0896R4_ranges_alg_transform_binary tests\P0896R4_ranges_alg_transform_unary +tests\P0896R4_ranges_alg_uninitialized_move tests\P0896R4_ranges_alg_unique tests\P0896R4_ranges_alg_unique_copy tests\P0896R4_ranges_iterator_machinery diff --git a/tests/std/tests/P0896R4_ranges_alg_uninitialized_move/env.lst b/tests/std/tests/P0896R4_ranges_alg_uninitialized_move/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_uninitialized_move/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0896R4_ranges_alg_uninitialized_move/test.cpp b/tests/std/tests/P0896R4_ranges_alg_uninitialized_move/test.cpp new file mode 100644 index 00000000000..773378f42f4 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_uninitialized_move/test.cpp @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +// Validate that uninitialized_move_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{})), + ranges::uninitialized_move_result>); +STATIC_ASSERT(same_as{}, borrowed{})), + ranges::uninitialized_move_result>); +STATIC_ASSERT(same_as{}, borrowed{})), + ranges::uninitialized_move_result>); +STATIC_ASSERT(same_as{}, borrowed{})), + ranges::uninitialized_move_result>); + +struct int_wrapper { + inline static int constructions = 0; + inline static int destructions = 0; + + static void clear_counts() { + constructions = 0; + destructions = 0; + } + + static constexpr int magic_throwing_val = 29; + int val = 10; + + int_wrapper() { + ++constructions; + } + int_wrapper(int x) : val{x} { + ++constructions; + } + + int_wrapper(int_wrapper&& that) { + if (that.val == magic_throwing_val) { + throw magic_throwing_val; + } + + val = exchange(that.val, -1); + ++constructions; + } + + ~int_wrapper() { + ++destructions; + } + + int_wrapper& operator=(int_wrapper&& that) { + if (that.val == magic_throwing_val) { + throw magic_throwing_val; + } + val = exchange(that.val, -1); + return *this; + } + + auto operator<=>(const int_wrapper&) const = default; +}; +STATIC_ASSERT(movable && !copyable); + +template +struct holder { + STATIC_ASSERT(N < ~size_t{0} / sizeof(T)); + alignas(T) unsigned char space[N * sizeof(T)]; + + auto as_span() { + return span{reinterpret_cast(space + 0), N}; + } +}; + +template +void not_ranges_destroy(R&& r) { // TRANSITION, ranges::destroy + for (auto& e : r) { + destroy_at(&e); + } +} + +struct instantiator { + static constexpr int expected_output[] = {13, 55, 12345}; + static constexpr int expected_input[] = {-1, -1, -1}; + + template + static void call() { + using ranges::uninitialized_move, ranges::uninitialized_move_result, ranges::equal, ranges::equal_to, + ranges::iterator_t; + + { // Validate range overload + int_wrapper input[3] = {13, 55, 12345}; + R wrapped_input{input}; + holder mem; + W wrapped_output{mem.as_span()}; + + int_wrapper::clear_counts(); + const same_as, iterator_t>> auto result = + uninitialized_move(wrapped_input, wrapped_output); + assert(int_wrapper::constructions == 3); + assert(int_wrapper::destructions == 0); + assert(result.in == wrapped_input.end()); + assert(result.out == wrapped_output.end()); + assert(equal(wrapped_output, expected_output, equal_to{}, &int_wrapper::val)); + assert(equal(input, expected_input, equal_to{}, &int_wrapper::val)); + not_ranges_destroy(wrapped_output); + assert(int_wrapper::constructions == 3); + assert(int_wrapper::destructions == 3); + } + + { // Validate iterator overload + int_wrapper input[3] = {13, 55, 12345}; + R wrapped_input{input}; + holder mem; + W wrapped_output{mem.as_span()}; + + int_wrapper::clear_counts(); + const same_as, iterator_t>> auto result = uninitialized_move( + wrapped_input.begin(), wrapped_input.end(), wrapped_output.begin(), wrapped_output.end()); + assert(int_wrapper::constructions == 3); + assert(int_wrapper::destructions == 0); + assert(result.in == wrapped_input.end()); + assert(result.out == wrapped_output.end()); + assert(equal(wrapped_output, expected_output, equal_to{}, &int_wrapper::val)); + assert(equal(input, expected_input, equal_to{}, &int_wrapper::val)); + not_ranges_destroy(wrapped_output); + assert(int_wrapper::constructions == 3); + assert(int_wrapper::destructions == 3); + } + } +}; + +struct throwing_test { + static constexpr int expected_input[] = {-1, -1, int_wrapper::magic_throwing_val, 12345}; + + template + static void call() { + // Validate only range overload (one is plenty since they both use the same backend) + int_wrapper input[] = {13, 55, int_wrapper::magic_throwing_val, 12345}; + R wrapped_input{input}; + holder mem; + W wrapped_output{mem.as_span()}; + + int_wrapper::clear_counts(); + try { + (void) ranges::uninitialized_move(wrapped_input, wrapped_output); + assert(false); + } catch (int i) { + assert(i == int_wrapper::magic_throwing_val); + } catch (...) { + assert(false); + } + assert(int_wrapper::constructions == 2); + assert(int_wrapper::destructions == 2); + assert(ranges::equal(input, expected_input, ranges::equal_to{}, &int_wrapper::val)); + } +}; + +template +using test_input = test::range; +using test_output = test::range; + +int main() { + // The algorithm is oblivious to non-required category, size, difference, and "proxyness" of the input range. It + // _is_ sensitive to proxyness in that it requires non-proxy references for the output range. + + instantiator::call, test_output>(); + instantiator::call, test_output>(); + throwing_test::call, test_output>(); + throwing_test::call, test_output>(); +} diff --git a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp index ddb4888f0a3..c0184d73292 100644 --- a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -983,3 +984,128 @@ namespace gh_1089 { } } } // namespace gh_1089 + +namespace special_memory_concepts { + // Validate the concepts from [special.mem.concepts] used to constrain the specialized memory algorithms + // NB: Non-portable tests of internal machinery + using ranges::_No_throw_input_iterator, ranges::_No_throw_sentinel_for, ranges::_No_throw_input_range, + ranges::_No_throw_forward_iterator, ranges::_No_throw_forward_range; + using std::forward_iterator, std::input_iterator, std::sentinel_for; + + enum class iterator_status : int { not_input, not_lvalue_reference, different_reference_and_value, input, forward }; + + template + struct iterator_archetype { + using iterator_concept = std::conditional_t>; + using difference_type = int; + using value_type = std::conditional_t; + + // clang-format off + int operator*() const requires (I == iterator_status::not_lvalue_reference); + int& operator*() const requires (I != iterator_status::not_lvalue_reference); + + iterator_archetype& operator++(); + void operator++(int) requires (I != iterator_status::forward); + iterator_archetype operator++(int) requires (I == iterator_status::forward); + + bool operator==(std::default_sentinel_t) const; + bool operator==(iterator_archetype const&) const requires (I == iterator_status::forward); + // clang-format on + }; + // Verify iterator_archetype + STATIC_ASSERT(!input_iterator>); + STATIC_ASSERT(input_iterator>); + STATIC_ASSERT(input_iterator>); + STATIC_ASSERT(input_iterator>); + STATIC_ASSERT(!forward_iterator>); + STATIC_ASSERT(forward_iterator>); + + template + inline constexpr bool has_lvalue_reference = std::is_lvalue_reference_v>; + STATIC_ASSERT(has_lvalue_reference>); + STATIC_ASSERT(!has_lvalue_reference>); + STATIC_ASSERT(has_lvalue_reference>); + STATIC_ASSERT(has_lvalue_reference>); + STATIC_ASSERT(has_lvalue_reference>); + + template + inline constexpr bool same_reference_value = + std::same_as>, std::iter_value_t>; + STATIC_ASSERT(same_reference_value>); + STATIC_ASSERT(same_reference_value>); + STATIC_ASSERT(!same_reference_value>); + STATIC_ASSERT(same_reference_value>); + STATIC_ASSERT(same_reference_value>); + + // Validate _No_throw_input_iterator + STATIC_ASSERT(!_No_throw_input_iterator>); + STATIC_ASSERT(!_No_throw_input_iterator>); + STATIC_ASSERT(!_No_throw_input_iterator>); + STATIC_ASSERT(_No_throw_input_iterator>); + STATIC_ASSERT(_No_throw_input_iterator>); + + enum class sentinel_status : int { no, yes }; + + template + struct sentinel_archetype { + // clang-format off + template + bool operator==(iterator_archetype const&) const requires (I != sentinel_status::no); + // clang-format on + }; + // Verify sentinel_archetype + STATIC_ASSERT(!sentinel_for, iterator_archetype>); + STATIC_ASSERT(sentinel_for, iterator_archetype>); + + // Validate _No_throw_sentinel_for + STATIC_ASSERT( + !_No_throw_sentinel_for, iterator_archetype>); + STATIC_ASSERT( + _No_throw_sentinel_for, iterator_archetype>); + STATIC_ASSERT(!_No_throw_sentinel_for, + iterator_archetype>); + STATIC_ASSERT(_No_throw_sentinel_for, + iterator_archetype>); + + // Validate _No_throw_forward_iterator + STATIC_ASSERT(!_No_throw_forward_iterator>); + STATIC_ASSERT(!_No_throw_forward_iterator>); + STATIC_ASSERT(!_No_throw_forward_iterator>); + STATIC_ASSERT(!_No_throw_forward_iterator>); + STATIC_ASSERT(_No_throw_forward_iterator>); + + enum class range_status : int { not_range, not_input, input, forward }; + + template + struct range_archetype { + using It = std::conditional_t, + std::conditional_t, + iterator_archetype>>>; + using Se = std::conditional_t, + std::default_sentinel_t>>; + + It begin() const; + Se end() const; + }; + // Verify range_archetype + STATIC_ASSERT(!ranges::range>); + STATIC_ASSERT(ranges::range>); + STATIC_ASSERT(ranges::range>); + STATIC_ASSERT(ranges::range>); + + // Validate _No_throw_input_range; note that the distinction betweeen range and + // no-throw-sentinel-for, iterator_t> is purely semantic, so we can't test them separately. + STATIC_ASSERT(!_No_throw_input_range>); + STATIC_ASSERT(!_No_throw_input_range>); + STATIC_ASSERT(_No_throw_input_range>); + STATIC_ASSERT(_No_throw_input_range>); + + // Validate _No_throw_forward_range + STATIC_ASSERT(!_No_throw_forward_range>); + STATIC_ASSERT(!_No_throw_forward_range>); + STATIC_ASSERT(!_No_throw_forward_range>); + STATIC_ASSERT(_No_throw_forward_range>); +} // namespace special_memory_concepts diff --git a/tests/std/tests/P0896R4_ranges_subrange/test.cpp b/tests/std/tests/P0896R4_ranges_subrange/test.cpp index d8bfe739a2a..961505a64ce 100644 --- a/tests/std/tests/P0896R4_ranges_subrange/test.cpp +++ b/tests/std/tests/P0896R4_ranges_subrange/test.cpp @@ -14,7 +14,7 @@ #include #include -#include "range_algorithm_support.hpp" +#include #define ASSERT(...) assert((__VA_ARGS__))