From 002cdc65a9fe476b1fc4527e405a659c7b147496 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Fri, 17 Jul 2020 06:54:37 +0200 Subject: [PATCH 01/50] Implement ranges::transform (#941) Co-authored-by: Stephan T. Lavavej --- stl/inc/algorithm | 114 ++++++++++++++++ tests/std/include/range_algorithm_support.hpp | 5 + tests/std/test.lst | 2 + .../tests/P0896R4_ranges_alg_copy/test.cpp | 5 +- .../tests/P0896R4_ranges_alg_copy_if/test.cpp | 6 +- .../tests/P0896R4_ranges_alg_move/test.cpp | 5 +- .../P0896R4_ranges_alg_replace_copy/test.cpp | 4 +- .../test.cpp | 4 +- .../env.lst | 4 + .../test.cpp | 128 ++++++++++++++++++ .../env.lst | 4 + .../test.cpp | 59 ++++++++ 12 files changed, 327 insertions(+), 13 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_transform_binary/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_transform_binary/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_transform_unary/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_transform_unary/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 945936ee546..b72e8a35a55 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -3266,6 +3266,120 @@ _FwdIt3 transform(_ExPo&& _Exec, const _FwdIt1 _First1, const _FwdIt1 _Last1, co _Fn _Func) noexcept; // terminates #endif // _HAS_CXX17 +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE unary_transform_result + template + using unary_transform_result = in_out_result<_In, _Out>; + + // ALIAS TEMPLATE binary_transform_result + template + using binary_transform_result = in_in_out_result<_In1, _In2, _Out>; + + // VARIABLE ranges::transform + class _Transform_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out, copy_constructible _Fn, + class _Pj = identity> + requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It, _Pj>>> + constexpr unary_transform_result<_It, _Out> operator()( + _It _First, _Se _Last, _Out _Result, _Fn _Func, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Transform_unary_unchecked(_Get_unwrapped(_STD move(_First)), + _Get_unwrapped(_STD move(_Last)), _STD move(_Result), _Pass_fn(_Func), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.out)}; + } + + template + requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected, _Pj>>> + constexpr unary_transform_result, _Out> operator()( + _Rng&& _Range, _Out _Result, _Fn _Func, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + auto _UResult = _Transform_unary_unchecked( + _Get_unwrapped(_STD move(_First)), _Uend(_Range), _STD move(_Result), _Pass_fn(_Func), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.out)}; + } + + template _Se1, input_iterator _It2, sentinel_for<_It2> _Se2, + weakly_incrementable _Out, copy_constructible _Fn, class _Pj1 = identity, class _Pj2 = identity> + requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It1, _Pj1>, projected<_It2, _Pj2>>> + constexpr binary_transform_result<_It1, _It2, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, + _Se2 _Last2, _Out _Result, _Fn _Func, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UResult = + _Transform_binary_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)), + _Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2)), _STD move(_Result), + _Pass_fn(_Func), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)}; + } + + template + requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected, _Pj1>, + projected, _Pj2>>> + constexpr binary_transform_result, borrowed_iterator_t<_Rng2>, _Out> operator()( + _Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Fn _Func, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + auto _First1 = _RANGES begin(_Range1); + auto _First2 = _RANGES begin(_Range2); + auto _UResult = _Transform_binary_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), + _Get_unwrapped(_STD move(_First2)), _Uend(_Range2), _STD move(_Result), _Pass_fn(_Func), + _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)}; + } + // clang-format on + + private: + template + _NODISCARD static constexpr unary_transform_result<_It, _Out> _Transform_unary_unchecked( + _It _First, const _Se _Last, _Out _Result, _Fn _Func, _Pj _Proj) { + // transform projected [_First, _Last) with _Func + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It, _Pj>>>); + + for (; _First != _Last; ++_First, (void) ++_Result) { + *_Result = _STD invoke(_Func, _STD invoke(_Proj, *_First)); + } + + return {_STD move(_First), _STD move(_Result)}; + } + + template + _NODISCARD static constexpr binary_transform_result<_It1, _It2, _Out> _Transform_binary_unchecked(_It1 _First1, + const _Se1 _Last1, _It2 _First2, const _Se2 _Last2, _Out _Result, _Fn _Func, _Pj1 _Proj1, _Pj2 _Proj2) { + // transform projected [_First1, _Last1) and projected [_First2, _Last2) with _Func + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT( + indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It1, _Pj1>, projected<_It2, _Pj2>>>); + + for (; _First1 != _Last1 && _First2 != _Last2; ++_First1, (void) ++_First2, ++_Result) { + *_Result = _STD invoke(_Func, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2)); + } + + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + }; + + inline constexpr _Transform_fn transform{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE replace template _CONSTEXPR20 void replace(const _FwdIt _First, const _FwdIt _Last, const _Ty& _Oldval, const _Ty& _Newval) { diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index d5fa87ddd08..4795d82cb3e 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -1018,6 +1018,11 @@ constexpr void test_read_write() { with_input_iterators, Element1>::call(); } +template +constexpr void test_in_in_write() { + with_input_ranges, Element2>, Element1>::call(); +} + template struct get_nth_fn { template diff --git a/tests/std/test.lst b/tests/std/test.lst index 4c9d6bf510c..ff3cf7ba453 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -273,6 +273,8 @@ tests\P0896R4_ranges_alg_replace_if tests\P0896R4_ranges_alg_search tests\P0896R4_ranges_alg_search_n tests\P0896R4_ranges_alg_swap_ranges +tests\P0896R4_ranges_alg_transform_binary +tests\P0896R4_ranges_alg_transform_unary tests\P0896R4_ranges_iterator_machinery tests\P0896R4_ranges_range_machinery tests\P0896R4_ranges_subrange diff --git a/tests/std/tests/P0896R4_ranges_alg_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_copy/test.cpp index 6fd9130e399..da5250e5256 100644 --- a/tests/std/tests/P0896R4_ranges_alg_copy/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_copy/test.cpp @@ -15,10 +15,9 @@ using namespace std; STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr))), - ranges::copy_result>); STATIC_ASSERT( - same_as{}, static_cast(nullptr))), ranges::copy_result>); + same_as{}, nullptr_to)), ranges::copy_result>); +STATIC_ASSERT(same_as{}, nullptr_to)), ranges::copy_result>); struct instantiator { static constexpr int input[3] = {13, 42, 1729}; diff --git a/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp index aa257403649..95795d1ab9a 100644 --- a/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp @@ -65,10 +65,10 @@ constexpr auto is_odd = [](int const x) { return x % 2 != 0; }; STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr), is_odd)), +STATIC_ASSERT(same_as{}, nullptr_to, is_odd)), ranges::copy_if_result>); -STATIC_ASSERT(same_as{}, static_cast(nullptr), is_odd)), - ranges::copy_if_result>); +STATIC_ASSERT( + same_as{}, nullptr_to, is_odd)), ranges::copy_if_result>); struct instantiator { static constexpr P input[3] = {{1, 99}, {4, 98}, {5, 97}}; diff --git a/tests/std/tests/P0896R4_ranges_alg_move/test.cpp b/tests/std/tests/P0896R4_ranges_alg_move/test.cpp index c97f016e4bc..34c485670ef 100644 --- a/tests/std/tests/P0896R4_ranges_alg_move/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_move/test.cpp @@ -27,10 +27,9 @@ struct int_wrapper { STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr))), - ranges::move_result>); STATIC_ASSERT( - same_as{}, static_cast(nullptr))), ranges::move_result>); + same_as{}, nullptr_to)), ranges::move_result>); +STATIC_ASSERT(same_as{}, nullptr_to)), ranges::move_result>); struct instantiator { static constexpr int_wrapper expected_output[3] = {13, 55, 12345}; diff --git a/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp index 965c5ecd5ac..b020fbcd8c2 100644 --- a/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp @@ -15,9 +15,9 @@ using P = pair; STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr), 42, 5)), +STATIC_ASSERT(same_as{}, nullptr_to, 42, 5)), ranges::replace_copy_result>); -STATIC_ASSERT(same_as{}, static_cast(nullptr), 42, 5)), +STATIC_ASSERT(same_as{}, nullptr_to, 42, 5)), ranges::replace_copy_result>); struct instantiator { diff --git a/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp index 0048304155c..57afc1c7226 100644 --- a/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp @@ -17,9 +17,9 @@ constexpr auto matches = [](int const val) { return val == 47; }; STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr), matches, 5)), +STATIC_ASSERT(same_as{}, nullptr_to, matches, 5)), ranges::replace_copy_if_result>); -STATIC_ASSERT(same_as{}, static_cast(nullptr), matches, 5)), +STATIC_ASSERT(same_as{}, nullptr_to, matches, 5)), ranges::replace_copy_if_result>); struct instantiator { diff --git a/tests/std/tests/P0896R4_ranges_alg_transform_binary/env.lst b/tests/std/tests/P0896R4_ranges_alg_transform_binary/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_transform_binary/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_transform_binary/test.cpp b/tests/std/tests/P0896R4_ranges_alg_transform_binary/test.cpp new file mode 100644 index 00000000000..11c31b21f0c --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_transform_binary/test.cpp @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +// Validate that binary_transform_result aliases in_in_out_result +STATIC_ASSERT(same_as, ranges::in_in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, plus{})), + ranges::binary_transform_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, plus{})), + ranges::binary_transform_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, plus{})), + ranges::binary_transform_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, plus{})), + ranges::binary_transform_result>); + +struct instantiator { + static constexpr P input1[3] = {{1, 99}, {4, 98}, {5, 97}}; + static constexpr P input2[3] = {{99, 6}, {98, 7}, {97, 8}}; + static constexpr int expected[3] = {7, 11, 13}; + + static constexpr P shortInput1[2] = {{1, 99}, {4, 98}}; + static constexpr P shortInput2[2] = {{99, 6}, {98, 7}}; + static constexpr int shortExpected[2] = {7, 11}; + + template + static constexpr void call() { + using ranges::transform, ranges::binary_transform_result, ranges::iterator_t; + { // Validate iterator + sentinel overload, first range shorter + int output[2] = {-1, -1}; + Read1 wrapped_in1{shortInput1}; + Read2 wrapped_in2{input2}; + + auto result = transform(wrapped_in1.begin(), wrapped_in1.end(), wrapped_in2.begin(), wrapped_in2.end(), + Write{output}, plus{}, get_first, get_second); + STATIC_ASSERT( + same_as, iterator_t, Write>>); + assert(result.in1 == wrapped_in1.end()); + assert(next(result.in2) == wrapped_in2.end()); + assert(result.out.peek() == output + 2); + assert(ranges::equal(output, shortExpected)); + } + { // Validate iterator + sentinel overload, second range shorter + int output[2] = {-1, -1}; + Read1 wrapped_in1{input1}; + Read2 wrapped_in2{shortInput2}; + + auto result = transform(wrapped_in1.begin(), wrapped_in1.end(), wrapped_in2.begin(), wrapped_in2.end(), + Write{output}, plus{}, get_first, get_second); + STATIC_ASSERT( + same_as, iterator_t, Write>>); + assert(next(result.in1) == wrapped_in1.end()); + assert(result.in2 == wrapped_in2.end()); + assert(result.out.peek() == output + 2); + assert(ranges::equal(output, shortExpected)); + } + { // Validate range overload, first range shorter + int output[2] = {-1, -1}; + Read1 wrapped_in1{shortInput1}; + Read2 wrapped_in2{input2}; + + auto result = transform(wrapped_in1, wrapped_in2, Write{output}, plus{}, get_first, get_second); + STATIC_ASSERT( + same_as, iterator_t, Write>>); + assert(result.in1 == wrapped_in1.end()); + assert(next(result.in2) == wrapped_in2.end()); + assert(result.out.peek() == output + 2); + assert(ranges::equal(output, shortExpected)); + } + { // Validate range overload, second range shorter + int output[2] = {-1, -1}; + Read1 wrapped_in1{input1}; + Read2 wrapped_in2{shortInput2}; + + auto result = transform(wrapped_in1, wrapped_in2, Write{output}, plus{}, get_first, get_second); + STATIC_ASSERT( + same_as, iterator_t, Write>>); + assert(next(result.in1) == wrapped_in1.end()); + assert(result.in2 == wrapped_in2.end()); + assert(result.out.peek() == output + 2); + assert(ranges::equal(output, shortExpected)); + } + } +}; + +using Elem1 = const P; +using Elem2 = const P; +using Elem3 = int; + +#ifdef TEST_EVERYTHING +int main() { + // No constexpr test here; the test_in_in_write call exceeds the maximum number of steps in a constexpr computation. + test_in_in_write(); +} +#else // ^^^ test all range combinations // test only interesting range combos vvv +template +using in_test_range = test::range; +template +using out_test_iterator = + test::iterator; + +constexpr bool run_tests() { + // All (except contiguous) proxy reference types, since the algorithm doesn't really care. + using test::Common, test::Sized; + + // both input, non-common, and sized or unsized + instantiator::call, in_test_range, out_test_iterator>(); + instantiator::call, in_test_range, out_test_iterator>(); + return true; +} + +int main() { + STATIC_ASSERT(run_tests()); + run_tests(); +} +#endif // TEST_EVERYTHING diff --git a/tests/std/tests/P0896R4_ranges_alg_transform_unary/env.lst b/tests/std/tests/P0896R4_ranges_alg_transform_unary/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_transform_unary/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_transform_unary/test.cpp b/tests/std/tests/P0896R4_ranges_alg_transform_unary/test.cpp new file mode 100644 index 00000000000..3d65ceb8996 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_transform_unary/test.cpp @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +// Validate that unary_transform_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +constexpr auto minus_one = [](const int x) { return x - 1; }; + +// Validate dangling story +STATIC_ASSERT(same_as{}, nullptr_to, minus_one)), + ranges::unary_transform_result>); +STATIC_ASSERT(same_as{}, nullptr_to, minus_one)), + ranges::unary_transform_result>); + +struct instantiator { + static constexpr P input[3] = {{1, 99}, {4, 98}, {5, 97}}; + static constexpr int expected[3] = {0, 3, 4}; + + template + static constexpr void call() { + using ranges::transform, ranges::unary_transform_result, ranges::iterator_t; + { // Validate iterator + sentinel overload + int output[3] = {-1, -1, -1}; + Read wrapped_in{input}; + + auto result = transform(wrapped_in.begin(), wrapped_in.end(), Write{output}, minus_one, get_first); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_in.end()); + assert(result.out.peek() == output + 3); + assert(ranges::equal(output, expected)); + } + { // Validate range overload + int output[3] = {-1, -1, -1}; + Read wrapped_in{input}; + + auto result = transform(wrapped_in, Write{output}, minus_one, get_first); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_in.end()); + assert(result.out.peek() == output + 3); + assert(ranges::equal(output, expected)); + } + } +}; + +int main() { + STATIC_ASSERT((test_in_write(), true)); + test_in_write(); +} From 16db9931bee67d59c772541bc731224d7c7cc478 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Fri, 17 Jul 2020 07:03:12 +0200 Subject: [PATCH 02/50] Implement ranges::remove family (#1005) --- stl/inc/algorithm | 302 ++++++++++++++++-- tests/std/include/range_algorithm_support.hpp | 5 + tests/std/test.lst | 4 + .../tests/P0896R4_ranges_alg_remove/env.lst | 4 + .../tests/P0896R4_ranges_alg_remove/test.cpp | 69 ++++ .../P0896R4_ranges_alg_remove_copy/env.lst | 4 + .../P0896R4_ranges_alg_remove_copy/test.cpp | 76 +++++ .../P0896R4_ranges_alg_remove_copy_if/env.lst | 4 + .../test.cpp | 77 +++++ .../P0896R4_ranges_alg_remove_if/env.lst | 4 + .../P0896R4_ranges_alg_remove_if/test.cpp | 71 ++++ 11 files changed, 599 insertions(+), 21 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_remove/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_remove/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_remove_copy/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_remove_copy/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_remove_copy_if/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_remove_copy_if/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_remove_if/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_remove_if/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index b72e8a35a55..1de012ae045 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -386,6 +386,21 @@ namespace ranges { inline constexpr _For_each_n_fn for_each_n{_Not_quite_object::_Construct_tag{}}; // VARIABLE ranges::find + // clang-format off + // concept-constrained for strict enforcement as it is used by several algorithms + template _Se, class _Ty, class _Pj> + requires indirect_binary_predicate, const _Ty*> + _NODISCARD constexpr _It _Find_unchecked(_It _First, const _Se _Last, const _Ty& _Val, _Pj _Proj) { + for (; _First != _Last; ++_First) { + if (_STD invoke(_Proj, *_First) == _Val) { + break; + } + } + + return _First; + } + // clang-format on + class _Find_fn : private _Not_quite_object { public: using _Not_quite_object::_Not_quite_object; @@ -395,16 +410,10 @@ namespace ranges { requires indirect_binary_predicate, const _Ty*> _NODISCARD constexpr _It operator()(_It _First, _Se _Last, const _Ty& _Val, _Pj _Proj = {}) const { _Adl_verify_range(_First, _Last); - // Performance note: investigate using _Find_unchecked when same_as<_Pj, identity> - auto _UFirst = _Get_unwrapped(_STD move(_First)); - const auto _ULast = _Get_unwrapped(_STD move(_Last)); - for (; _UFirst != _ULast; ++_UFirst) { - if (_STD invoke(_Proj, *_UFirst) == _Val) { - break; - } - } + auto _UResult = _RANGES _Find_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Val, _Pass_fn(_Proj)); - _Seek_wrapped(_First, _STD move(_UFirst)); + _Seek_wrapped(_First, _STD move(_UResult)); return _First; } @@ -412,7 +421,12 @@ namespace ranges { requires indirect_binary_predicate, _Pj>, const _Ty*> _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()( _Rng&& _Range, const _Ty& _Val, _Pj _Proj = {}) const { - return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Val, _Pass_fn(_Proj)); + auto _First = _RANGES begin(_Range); + auto _UResult = + _RANGES _Find_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), _Val, _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; } // clang-format on }; @@ -430,6 +444,18 @@ _NODISCARD _FwdIt find_if(_ExPo&& _Exec, _FwdIt _First, const _FwdIt _Last, _Pr #ifdef __cpp_lib_concepts namespace ranges { // VARIABLE ranges::find_if + // concept-constrained for strict enforcement as it is used by several algorithms + template _Se, class _Pj, indirect_unary_predicate> _Pr> + _NODISCARD constexpr _It _Find_if_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + for (; _First != _Last; ++_First) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + break; + } + } + + return _First; + } + class _Find_if_fn : private _Not_quite_object { public: using _Not_quite_object::_Not_quite_object; @@ -438,22 +464,22 @@ namespace ranges { indirect_unary_predicate> _Pr> _NODISCARD constexpr _It operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_STD move(_First)); - const auto _ULast = _Get_unwrapped(_STD move(_Last)); - for (; _UFirst != _ULast; ++_UFirst) { - if (_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst))) { - break; - } - } + auto _UResult = _RANGES _Find_if_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); - _Seek_wrapped(_First, _STD move(_UFirst)); + _Seek_wrapped(_First, _STD move(_UResult)); return _First; } template , _Pj>> _Pr> _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { - return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _First = _RANGES begin(_Range); + auto _UResult = _RANGES _Find_if_unchecked( + _Get_unwrapped(_STD move(_First)), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; } }; @@ -3985,13 +4011,247 @@ _FwdIt2 remove_copy_if(_ExPo&&, _FwdIt1 _First, _FwdIt1 _Last, _FwdIt2 _Dest, _P template = 0> _NODISCARD _FwdIt remove( _ExPo&& _Exec, const _FwdIt _First, const _FwdIt _Last, const _Ty& _Val) noexcept; // terminates -#endif // _HAS_CXX17 -#if _HAS_CXX17 template = 0> _NODISCARD _FwdIt remove_if(_ExPo&& _Exec, _FwdIt _First, const _FwdIt _Last, _Pr _Pred) noexcept; // terminates #endif // _HAS_CXX17 +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::remove + class _Remove_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, class _Ty, class _Pj = identity> + requires indirect_binary_predicate, const _Ty*> + _NODISCARD constexpr subrange<_It> operator()(_It _First, _Se _Last, const _Ty& _Val, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Remove_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Val, _Pass_fn(_Proj)); + + return _Rewrap_subrange>(_First, _STD move(_UResult)); + } + + template + requires permutable> + && indirect_binary_predicate, _Pj>, const _Ty*> + _NODISCARD constexpr borrowed_subrange_t<_Rng> operator()( + _Rng&& _Range, const _Ty& _Val, _Pj _Proj = {}) const { + auto _UResult = _Remove_unchecked(_Ubegin(_Range), _Uend(_Range), _Val, _Pass_fn(_Proj)); + + return _Rewrap_subrange>(_Range, _STD move(_UResult)); + } + // clang-format on + private: + template + _NODISCARD static constexpr subrange<_It> _Remove_unchecked( + _It _First, const _Se _Last, const _Ty& _Val, _Pj _Proj) { + // Remove projected values equal to _Val from [_First, _Last) + _STL_INTERNAL_STATIC_ASSERT(permutable<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_binary_predicate, const _Ty*>); + + _First = _RANGES _Find_unchecked(_STD move(_First), _Last, _Val, _Proj); + auto _Next = _First; + if (_First == _Last) { + return {_STD move(_Next), _STD move(_First)}; + } + + while (++_First != _Last) { + if (_STD invoke(_Proj, *_First) != _Val) { + *_Next = _RANGES iter_move(_First); + ++_Next; + } + } + + return {_STD move(_Next), _STD move(_First)}; + } + }; + + inline constexpr _Remove_fn remove{_Not_quite_object::_Construct_tag{}}; + + // VARIABLE ranges::remove_if + class _Remove_if_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Pj = identity, + indirect_unary_predicate> _Pr> + _NODISCARD constexpr subrange<_It> operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Remove_if_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + return _Rewrap_subrange>(_First, _STD move(_UResult)); + } + + // clang-format off + template , _Pj>> _Pr> + requires permutable> + _NODISCARD constexpr borrowed_subrange_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { + auto _UResult = _Remove_if_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + return _Rewrap_subrange>(_Range, _STD move(_UResult)); + } + // clang-format on + private: + template + _NODISCARD static constexpr subrange<_It> _Remove_if_unchecked( + _It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + // Remove values whose projection satisfies _Pred from [_First, _Last) + _STL_INTERNAL_STATIC_ASSERT(permutable<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + + _First = _RANGES _Find_if_unchecked(_STD move(_First), _Last, _Pred, _Proj); + auto _Next = _First; + if (_First == _Last) { + return {_STD move(_Next), _STD move(_First)}; + } + + while (++_First != _Last) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + *_Next = _RANGES iter_move(_First); + ++_Next; + } + } + + return {_STD move(_Next), _STD move(_First)}; + } + }; + + inline constexpr _Remove_if_fn remove_if{_Not_quite_object::_Construct_tag{}}; + + // ALIAS TEMPLATE remove_copy_result + template + using remove_copy_result = in_out_result<_In, _Out>; + + // VARIABLE ranges::remove_copy + class _Remove_copy_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out, class _Ty, class _Pj = identity> + requires indirectly_copyable<_It, _Out> + && indirect_binary_predicate, const _Ty*> + constexpr remove_copy_result<_It, _Out> operator()( + _It _First, _Se _Last, _Out _Result, const _Ty& _Val, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Remove_copy_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), + _Get_unwrapped_unverified(_STD move(_Result)), _Val, _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First), _STD move(_Result)}; + } + + template + requires indirectly_copyable, _Out> + && indirect_binary_predicate, _Pj>, const _Ty*> + constexpr remove_copy_result, _Out> operator()( + _Rng&& _Range, _Out _Result, const _Ty& _Val, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + auto _UResult = _Remove_copy_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), + _Get_unwrapped_unverified(_STD move(_Result)), _Val, _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First), _STD move(_Result)}; + } + // clang-format on + private: + template + _NODISCARD static constexpr remove_copy_result<_It, _Out> _Remove_copy_unchecked( + _It _First, const _Se _Last, _Out _Result, const _Ty& _Val, _Pj _Proj) { + // Copy [_First, _Last) to _Result except projected values equal to _Val + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_copyable<_It, _Out>); + _STL_INTERNAL_STATIC_ASSERT(indirect_binary_predicate, const _Ty*>); + + for (; _First != _Last; ++_First) { + if (_STD invoke(_Proj, *_First) != _Val) { + *_Result = *_First; + ++_Result; + } + } + + return {_STD move(_First), _STD move(_Result)}; + } + }; + + inline constexpr _Remove_copy_fn remove_copy{_Not_quite_object::_Construct_tag{}}; + + // ALIAS TEMPLATE remove_copy_if_result + template + using remove_copy_if_result = in_out_result<_In, _Out>; + + // VARIABLE ranges::remove_copy_if + class _Remove_copy_if_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out, class _Pj = identity, + indirect_unary_predicate> _Pr> + requires indirectly_copyable<_It, _Out> + constexpr remove_copy_if_result<_It, _Out> operator()( + _It _First, _Se _Last, _Out _Result, _Pr _Pred, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = + _Remove_copy_if_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), + _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First), _STD move(_Result)}; + } + + template , _Pj>> _Pr> + requires indirectly_copyable, _Out> + constexpr remove_copy_if_result, _Out> operator()( + _Rng&& _Range, _Out _Result, _Pr _Pred, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + auto _UResult = _Remove_copy_if_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), + _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First), _STD move(_Result)}; + } + // clang-format on + private: + template + _NODISCARD static constexpr remove_copy_if_result<_It, _Out> _Remove_copy_if_unchecked( + _It _First, const _Se _Last, _Out _Result, _Pr _Pred, _Pj _Proj) { + // Copy [_First, _Last) to _Result except projected values that satisfy _Pred + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_copyable<_It, _Out>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + + for (; _First != _Last; ++_First) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + *_Result = *_First; + ++_Result; + } + } + + return {_STD move(_First), _STD move(_Result)}; + } + }; + + inline constexpr _Remove_copy_if_fn remove_copy_if{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE unique template _NODISCARD _CONSTEXPR20 _FwdIt unique(_FwdIt _First, _FwdIt _Last, _Pr _Pred) { diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index 4795d82cb3e..58febafc5fc 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -1008,6 +1008,11 @@ constexpr void test_in_write() { with_input_ranges, Element1>::call(); } +template +constexpr void test_fwd_write() { + with_forward_ranges, Element1>::call(); +} + template constexpr void test_read() { with_input_iterators::call(); diff --git a/tests/std/test.lst b/tests/std/test.lst index ff3cf7ba453..13dd4a72345 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -266,6 +266,10 @@ tests\P0896R4_ranges_alg_none_of tests\P0896R4_ranges_alg_partition tests\P0896R4_ranges_alg_partition_copy tests\P0896R4_ranges_alg_partition_point +tests\P0896R4_ranges_alg_remove +tests\P0896R4_ranges_alg_remove_copy +tests\P0896R4_ranges_alg_remove_copy_if +tests\P0896R4_ranges_alg_remove_if tests\P0896R4_ranges_alg_replace tests\P0896R4_ranges_alg_replace_copy tests\P0896R4_ranges_alg_replace_copy_if diff --git a/tests/std/tests/P0896R4_ranges_alg_remove/env.lst b/tests/std/tests/P0896R4_ranges_alg_remove/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_remove/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_remove/test.cpp b/tests/std/tests/P0896R4_ranges_alg_remove/test.cpp new file mode 100644 index 00000000000..3ddb3c33703 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_remove/test.cpp @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include +using namespace std; +using P = pair; + +// Validate dangling story +STATIC_ASSERT(same_as{}, 42)), ranges::dangling>); +STATIC_ASSERT(same_as{}, 42)), ranges::subrange>); + +struct instantiator { + static constexpr P expected[3] = {{0, 99}, {2, 99}, {4, 99}}; + + template + static constexpr void call() { +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 +#pragma warning(suppress : 4127) // conditional expression is constant + if (!ranges::contiguous_range || !is_constant_evaluated()) +#endif // TRANSITION, VSO-938163 + { + using ranges::remove, ranges::subrange, ranges::equal, ranges::iterator_t; + + size_t projectionCounter = 0; + auto projection = [&projectionCounter](const P& val) { + ++projectionCounter; + return val.second; + }; + + { // Validate iterator + sentinel overload + P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; + Read wrapped_input{input}; + + auto result = remove(wrapped_input.begin(), wrapped_input.end(), 47, projection); + STATIC_ASSERT(same_as>>); + assert(result.begin() == next(wrapped_input.begin(), 3)); + assert(result.end() == wrapped_input.end()); + assert(equal(expected, span{input}.first<3>())); + assert(projectionCounter == ranges::size(input)); + } + + projectionCounter = 0; + + { // Validate range overload + P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; + Read wrapped_input{input}; + + auto result = remove(wrapped_input, 47, projection); + STATIC_ASSERT(same_as>>); + assert(result.begin() == next(wrapped_input.begin(), 3)); + assert(result.end() == wrapped_input.end()); + assert(equal(expected, span{input}.first<3>())); + assert(projectionCounter == ranges::size(input)); + } + } + } +}; + +int main() { + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_remove_copy/env.lst b/tests/std/tests/P0896R4_ranges_alg_remove_copy/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_remove_copy/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_remove_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_remove_copy/test.cpp new file mode 100644 index 00000000000..e5228540ed6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_remove_copy/test.cpp @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include +using namespace std; +using P = pair; + +// Validate that remove_copy_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, static_cast(nullptr), 42)), + ranges::remove_copy_result>); +STATIC_ASSERT(same_as{}, static_cast(nullptr), 42)), + ranges::remove_copy_result>); + +struct instantiator { + static constexpr P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; + static constexpr P expected[3] = {{0, 99}, {2, 99}, {4, 99}}; + + template > Write> + static constexpr void call() { + // Fails checking the indirect_binary_predicate requirement in C1XX's permissive mode with proxy iterators + // (probably related to VSO-566808) + constexpr bool non_proxy = + is_reference_v> && is_reference_v>; + if constexpr (non_proxy || !is_permissive) { + using ranges::remove_copy, ranges::remove_copy_result, ranges::equal, ranges::iterator_t; + + size_t projectionCounter = 0; + auto projection = [&projectionCounter](const P& val) { + ++projectionCounter; + return val.second; + }; + + { // Validate iterator + sentinel overload + P output[3] = {{-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + + auto result = remove_copy(wrapped_input.begin(), wrapped_input.end(), Write{output}, 47, projection); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == output + 3); + assert(equal(output, expected)); + assert(projectionCounter == ranges::size(input)); + } + + projectionCounter = 0; + + { // Validate range overload + P output[3] = {{-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + + auto result = remove_copy(wrapped_input, Write{output}, 47, projection); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == output + 3); + assert(equal(output, expected)); + assert(projectionCounter == ranges::size(input)); + } + } + } +}; + +int main() { +#ifndef _PREFAST_ // TRANSITION, GH-1030 + STATIC_ASSERT((test_in_write(), true)); +#endif // TRANSITION, GH-1030 + test_in_write(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_remove_copy_if/env.lst b/tests/std/tests/P0896R4_ranges_alg_remove_copy_if/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_remove_copy_if/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_remove_copy_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_remove_copy_if/test.cpp new file mode 100644 index 00000000000..23453dccd76 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_remove_copy_if/test.cpp @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include +using namespace std; +using P = pair; + +constexpr auto matches = [](const int val) { return val == 47; }; + +// Validate that remove_copy_if_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, static_cast(nullptr), matches)), + ranges::remove_copy_if_result>); +STATIC_ASSERT(same_as{}, static_cast(nullptr), matches)), + ranges::remove_copy_if_result>); + +struct instantiator { + static constexpr P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; + static constexpr P expected[3] = {{0, 99}, {2, 99}, {4, 99}}; + + template > Write> + static constexpr void call() { + // Fails checking the indirect_binary_predicate requirement in C1XX's permissive mode with proxy iterators + // (probably related to VSO-566808) + constexpr bool non_proxy = + is_reference_v> && is_reference_v>; + if constexpr (non_proxy || !is_permissive) { + using ranges::remove_copy_if, ranges::remove_copy_if_result, ranges::equal, ranges::iterator_t; + + size_t projectionCounter = 0; + auto projection = [&projectionCounter](const P& val) { + ++projectionCounter; + return val.second; + }; + + { // Validate iterator + sentinel overload + P output[3] = {{-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + auto result = + remove_copy_if(wrapped_input.begin(), wrapped_input.end(), Write{output}, matches, projection); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == output + 3); + assert(equal(output, expected)); + assert(projectionCounter == ranges::size(input)); + } + + projectionCounter = 0; + + { // Validate range overload + P output[3] = {{-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + auto result = remove_copy_if(wrapped_input, Write{output}, matches, projection); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == output + 3); + assert(equal(output, expected)); + assert(projectionCounter == ranges::size(input)); + } + } + } +}; + +int main() { +#ifndef _PREFAST_ // TRANSITION, GH-1030 + STATIC_ASSERT((test_in_write(), true)); +#endif // TRANSITION, GH-1030 + test_in_write(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_remove_if/env.lst b/tests/std/tests/P0896R4_ranges_alg_remove_if/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_remove_if/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_remove_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_remove_if/test.cpp new file mode 100644 index 00000000000..863a0e28917 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_remove_if/test.cpp @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include +using namespace std; +using P = pair; + +constexpr auto matches = [](const int val) { return val == 47; }; + +// Validate dangling story +STATIC_ASSERT(same_as{}, matches)), ranges::dangling>); +STATIC_ASSERT(same_as{}, matches)), ranges::subrange>); + +struct instantiator { + static constexpr P expected[3] = {{0, 99}, {2, 99}, {4, 99}}; + + template + static constexpr void call() { +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 +#pragma warning(suppress : 4127) // conditional expression is constant + if (!ranges::contiguous_range || !is_constant_evaluated()) +#endif // TRANSITION, VSO-938163 + { + using ranges::remove_if, ranges::subrange, ranges::equal, ranges::iterator_t; + + size_t projectionCounter = 0; + auto projection = [&projectionCounter](const P& val) { + ++projectionCounter; + return val.second; + }; + + { // Validate iterator + sentinel overload + P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; + Read wrapped_input{input}; + + auto result = remove_if(wrapped_input.begin(), wrapped_input.end(), matches, projection); + STATIC_ASSERT(same_as>>); + assert(result.begin() == next(wrapped_input.begin(), 3)); + assert(result.end() == wrapped_input.end()); + assert(equal(expected, span{input}.first<3>())); + assert(projectionCounter == ranges::size(input)); + } + + projectionCounter = 0; + + { // Validate range overload + P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; + Read wrapped_input{input}; + + auto result = remove_if(wrapped_input, matches, projection); + STATIC_ASSERT(same_as>>); + assert(result.begin() == next(wrapped_input.begin(), 3)); + assert(result.end() == wrapped_input.end()); + assert(equal(expected, span{input}.first<3>())); + assert(projectionCounter == ranges::size(input)); + } + } + } +}; + +int main() { + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); +} From 0965adad9ac06b501b7073c94a0e52f337ed8752 Mon Sep 17 00:00:00 2001 From: Christian Fersch Date: Fri, 17 Jul 2020 07:06:09 +0200 Subject: [PATCH 03/50] Use permissive wide-to-narrow transcoding in filesystem_error (#1010) --- stl/inc/filesystem | 34 +++++++++++++++++-- stl/inc/xfilesystem_abi.h | 4 +++ stl/src/filesystem.cpp | 21 ++++++++++++ tests/std/test.lst | 1 + .../env.lst | 4 +++ .../test.cpp | 33 ++++++++++++++++++ 6 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tests/std/tests/GH_001010_filesystem_error_encoding/env.lst create mode 100644 tests/std/tests/GH_001010_filesystem_error_encoding/test.cpp diff --git a/stl/inc/filesystem b/stl/inc/filesystem index 8b93dd566d4..a2f4bd1dd09 100644 --- a/stl/inc/filesystem +++ b/stl/inc/filesystem @@ -102,6 +102,32 @@ namespace filesystem { return _Output; } + // More lenient version of _Convert_wide_to_narrow: Instead of failing on non-representable characters, + // replace them with a replacement character. + template + _NODISCARD basic_string _Convert_wide_to_narrow_replace_chars( + const __std_code_page _Code_page, const wstring_view _Input, const _Alloc& _Al) { + basic_string _Output(_Al); + + if (!_Input.empty()) { + if (_Input.size() > static_cast(INT_MAX)) { + _Throw_system_error(errc::invalid_argument); + } + + const int _Len = _Check_convert_result(__std_fs_convert_wide_to_narrow_replace_chars( + _Code_page, _Input.data(), static_cast(_Input.size()), nullptr, 0)); + + _Output.resize(static_cast(_Len)); + + const auto _Data_as_char = reinterpret_cast(_Output.data()); + + (void) _Check_convert_result(__std_fs_convert_wide_to_narrow_replace_chars( + _Code_page, _Input.data(), static_cast(_Input.size()), _Data_as_char, _Len)); + } + + return _Output; + } + _NODISCARD inline wstring _Convert_utf32_to_wide(const u32string_view _Input) { wstring _Output; @@ -1786,8 +1812,12 @@ namespace filesystem { static string _Pretty_message(const string_view _Op, const path& _Path1, const path& _Path2 = {}) { using namespace _STD string_view_literals; // TRANSITION, VSO-571749 string _Result; - const string _Path1_str = _Path1.string(); - const string _Path2_str = _Path2.string(); + // Convert the paths to narrow encoding in a way that gracefully handles non-encodable characters + const auto _Code_page = __std_fs_code_page(); + const string _Path1_str = _Convert_wide_to_narrow_replace_chars>( + _Code_page, _Path1.native(), allocator{}); + const string _Path2_str = _Convert_wide_to_narrow_replace_chars>( + _Code_page, _Path2.native(), allocator{}); _Result.reserve(_Op.size() + (_Path2_str.empty() ? 4 : 8) + _Path1_str.size() + _Path2_str.size()); _Result += _Op; _Result += R"(: ")"sv; // 3 chars diff --git a/stl/inc/xfilesystem_abi.h b/stl/inc/xfilesystem_abi.h index 0d3ce7035a9..2e3fcddde59 100644 --- a/stl/inc/xfilesystem_abi.h +++ b/stl/inc/xfilesystem_abi.h @@ -276,6 +276,10 @@ _NODISCARD __std_fs_convert_result __stdcall __std_fs_convert_wide_to_narrow(_In _In_reads_(_Input_len) const wchar_t* _Input_str, _In_ int _Input_len, _Out_writes_opt_(_Output_len) char* _Output_str, _In_ int _Output_len) noexcept; +_NODISCARD __std_fs_convert_result __stdcall __std_fs_convert_wide_to_narrow_replace_chars( + _In_ __std_code_page _Code_page, _In_reads_(_Input_len) const wchar_t* _Input_str, _In_ int _Input_len, + _Out_writes_opt_(_Output_len) char* _Output_str, _In_ int _Output_len) noexcept; + _NODISCARD __std_win_error __stdcall __std_fs_get_file_id( _Out_ __std_fs_file_id* _Id, _In_z_ const wchar_t* _Path) noexcept; diff --git a/stl/src/filesystem.cpp b/stl/src/filesystem.cpp index ca312314f04..0a1d3d5a1e8 100644 --- a/stl/src/filesystem.cpp +++ b/stl/src/filesystem.cpp @@ -421,6 +421,27 @@ void __stdcall __std_fs_directory_iterator_close(const __std_fs_dir_handle _Hand return _Result; } +[[nodiscard]] __std_fs_convert_result __stdcall __std_fs_convert_wide_to_narrow_replace_chars( + const __std_code_page _Code_page, const wchar_t* const _Input_str, const int _Input_len, char* const _Output_str, + const int _Output_len) noexcept { + __std_fs_convert_result _Result; + + _Result._Len = WideCharToMultiByte(static_cast(_Code_page), WC_NO_BEST_FIT_CHARS, _Input_str, + _Input_len, _Output_str, _Output_len, nullptr, nullptr); + + _Result._Err = _Result._Len == 0 ? __std_win_error{GetLastError()} : __std_win_error::_Success; + + // Some codepages don't support WC_NO_BEST_FIT_CHARS, fall back to default conversion. + if (_Result._Err == __std_win_error{ERROR_INVALID_FLAGS}) { + _Result._Len = WideCharToMultiByte(static_cast(_Code_page), 0, _Input_str, _Input_len, + _Output_str, _Output_len, nullptr, nullptr); + + _Result._Err = _Result._Len == 0 ? __std_win_error{GetLastError()} : __std_win_error::_Success; + } + + return _Result; +} + [[nodiscard]] __std_fs_copy_file_result __stdcall __std_fs_copy_file(const wchar_t* const _Source, const wchar_t* const _Target, __std_fs_copy_options _Options) noexcept { // copy _Source to _Target _Options &= __std_fs_copy_options::_Existing_mask; diff --git a/tests/std/test.lst b/tests/std/test.lst index 13dd4a72345..db59922818f 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -160,6 +160,7 @@ tests\GH_000545_include_compare tests\GH_000685_condition_variable_any tests\GH_000690_overaligned_function tests\GH_000890_pow_template +tests\GH_001010_filesystem_error_encoding tests\LWG2597_complex_branch_cut tests\LWG3018_shared_ptr_function tests\P0024R2_parallel_algorithms_adjacent_difference diff --git a/tests/std/tests/GH_001010_filesystem_error_encoding/env.lst b/tests/std/tests/GH_001010_filesystem_error_encoding/env.lst new file mode 100644 index 00000000000..2de7aab2959 --- /dev/null +++ b/tests/std/tests/GH_001010_filesystem_error_encoding/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_17_matrix.lst diff --git a/tests/std/tests/GH_001010_filesystem_error_encoding/test.cpp b/tests/std/tests/GH_001010_filesystem_error_encoding/test.cpp new file mode 100644 index 00000000000..a823c2599a2 --- /dev/null +++ b/tests/std/tests/GH_001010_filesystem_error_encoding/test.cpp @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +using namespace std; +namespace fs = std::filesystem; + +void test_filesystem_error_with_bad_codepage_characters() { + fs::path problem_path{L"problematic\xD83Dtestpath"}; // path containing lone high surrogate + + // Make sure the path is not encodable in our current locale, otherwise the test tests nothing + bool exception_caught = false; + try { + (void) problem_path.string(); + } catch (const exception&) { + exception_caught = true; + } + assert(exception_caught); + + // filesystem_error should handle the non-encodable character gracefully when building its message + fs::filesystem_error err{"testexception", problem_path, error_code{}}; + assert(string_view{err.what()}.find("problematic") != string_view::npos); + assert(string_view{err.what()}.find("testpath") != string_view::npos); +} + +int main() { + test_filesystem_error_with_bad_codepage_characters(); +} From 21acd23ad81463628e95cb6c571c56ba7200cdba Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 16 Jul 2020 22:10:13 -0700 Subject: [PATCH 04/50] Implement ranges::copy_backward (#1026) * Implement ranges::copy_backward Co-authored-by: Stephan T. Lavavej --- stl/inc/algorithm | 52 +++++++-- stl/inc/xutility | 29 +++-- stl/inc/yvals_core.h | 2 +- tests/std/include/range_algorithm_support.hpp | 98 +++++++++++++---- tests/std/test.lst | 1 + .../P0896R4_ranges_alg_copy_backward/env.lst | 4 + .../P0896R4_ranges_alg_copy_backward/test.cpp | 104 ++++++++++++++++++ .../tests/P0896R4_ranges_alg_copy_if/test.cpp | 2 + .../tests/P0896R4_ranges_alg_heap/test.cpp | 60 ++++++---- .../P0896R4_ranges_alg_mismatch/test.cpp | 2 + .../tests/P0896R4_ranges_alg_move/test.cpp | 53 +++++---- .../P0896R4_ranges_alg_replace_copy/test.cpp | 13 +-- .../test.cpp | 13 +-- .../P0896R4_ranges_test_machinery/test.cpp | 11 +- tests/std/tests/P0898R3_concepts/test.cpp | 3 +- tests/std/tests/concepts_matrix.lst | 4 +- tests/tr1/include/tspec_random.h | 3 +- 17 files changed, 341 insertions(+), 113 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_copy_backward/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_copy_backward/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 1de012ae045..80a0ce54552 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1580,8 +1580,9 @@ namespace ranges { template requires indirectly_copyable, _Out> constexpr copy_result, _Out> operator()(_Rng&& _Range, _Out _Result) const { - auto _First = _RANGES begin(_Range); - auto _UResult = _RANGES _Copy_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), _STD move(_Result)); + auto _First = _RANGES begin(_Range); + auto _UResult = + _RANGES _Copy_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), _STD move(_Result)); _Seek_wrapped(_First, _STD move(_UResult.in)); return {_STD move(_First), _STD move(_UResult.out)}; } @@ -1615,6 +1616,39 @@ namespace ranges { }; inline constexpr _Copy_n_fn copy_n{_Not_quite_object::_Construct_tag{}}; + + // ALIAS TEMPLATE copy_backward_result + template + using copy_backward_result = in_out_result<_In, _Out>; + + // VARIABLE ranges::copy_backward + class _Copy_backward_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se1, bidirectional_iterator _It2> + requires indirectly_copyable<_It1, _It2> + constexpr copy_backward_result<_It1, _It2> operator()(_It1 _First, _Se1 _Last, _It2 _Result) const { + _Adl_verify_range(_First, _Last); + auto _UFirst = _Get_unwrapped(_STD move(_First)); + auto _ULast = _Get_final_iterator_unwrapped<_It1>(_UFirst, _STD move(_Last)); + _Seek_wrapped(_First, _ULast); + _Result = _Copy_backward_unchecked(_STD move(_UFirst), _STD move(_ULast), _STD move(_Result)); + return {_STD move(_First), _STD move(_Result)}; + } + + template + requires indirectly_copyable, _It> + constexpr copy_backward_result, _It> operator()(_Rng&& _Range, _It _Result) const { + auto _ULast = _Get_final_iterator_unwrapped(_Range); + _Result = _Copy_backward_unchecked(_Ubegin(_Range), _ULast, _STD move(_Result)); + return {_Rewrap_iterator(_Range, _STD move(_ULast)), _STD move(_Result)}; + } + // clang-format on + }; + + inline constexpr _Copy_backward_fn copy_backward{_Not_quite_object::_Construct_tag{}}; } // namespace ranges #endif // __cpp_lib_concepts @@ -2599,7 +2633,8 @@ namespace ranges { if constexpr (sized_sentinel_for<_Se, _It>) { const auto _Dist = _ULast - _UFirst; - auto _UResult = _Search_n_sized(_STD move(_UFirst), _Dist, _Val, _Count, _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _UResult = + _Search_n_sized(_STD move(_UFirst), _Dist, _Val, _Count, _Pass_fn(_Pred), _Pass_fn(_Proj)); return _Rewrap_subrange>(_First, _STD move(_UResult)); } else { auto _UResult = _Search_n_unsized( @@ -2610,8 +2645,8 @@ namespace ranges { template requires indirectly_comparable, const _Ty*, _Pr, _Pj> - _NODISCARD constexpr borrowed_subrange_t<_Rng> operator()( - _Rng&& _Range, const range_difference_t<_Rng> _Count, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + _NODISCARD constexpr borrowed_subrange_t<_Rng> operator()(_Rng&& _Range, const range_difference_t<_Rng> _Count, + const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { auto _First = _RANGES begin(_Range); if (_Count <= 0) { @@ -2625,8 +2660,8 @@ namespace ranges { _Search_n_sized(_Get_unwrapped(_First), _Dist, _Val, _Count, _Pass_fn(_Pred), _Pass_fn(_Proj)); return _Rewrap_subrange>(_First, _STD move(_UResult)); } else { - auto _UResult = _Search_n_unsized(_Get_unwrapped(_First), _Uend(_Range), _Val, - _Count, _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _UResult = _Search_n_unsized( + _Get_unwrapped(_First), _Uend(_Range), _Val, _Count, _Pass_fn(_Pred), _Pass_fn(_Proj)); return _Rewrap_subrange>(_First, _STD move(_UResult)); } } @@ -4936,7 +4971,8 @@ namespace ranges { } // clang-format off - template , _Pj>> _Pr> + template , _Pj>> _Pr> requires permutable> constexpr borrowed_subrange_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { auto _UResult = _Partition_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); diff --git a/stl/inc/xutility b/stl/inc/xutility index 26299d2612e..0728eaacc6c 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -4471,27 +4471,32 @@ _BidIt2 _Copy_backward_memmove(move_iterator<_BidIt1> _First, move_iterator<_Bid #if _HAS_IF_CONSTEXPR template -_CONSTEXPR20 _BidIt2 copy_backward(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest) { +_NODISCARD _CONSTEXPR20 _BidIt2 _Copy_backward_unchecked(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest) { // copy [_First, _Last) backwards to [..., _Dest) - _Adl_verify_range(_First, _Last); - const auto _UFirst = _Get_unwrapped(_First); - auto _ULast = _Get_unwrapped(_Last); - auto _UDest = _Get_unwrapped_n(_Dest, -_Idl_distance<_BidIt1>(_UFirst, _ULast)); - if constexpr (_Ptr_copy_cat::_Trivially_copyable) { + if constexpr (_Ptr_copy_cat<_BidIt1, _BidIt2>::_Trivially_copyable) { #ifdef __cpp_lib_is_constant_evaluated if (!_STD is_constant_evaluated()) #endif // __cpp_lib_is_constant_evaluated { - _UDest = _Copy_backward_memmove(_UFirst, _ULast, _UDest); - _Seek_wrapped(_Dest, _UDest); - return _Dest; + return _Copy_backward_memmove(_First, _Last, _Dest); } } - while (_UFirst != _ULast) { - *--_UDest = *--_ULast; + while (_First != _Last) { + *--_Dest = *--_Last; } - _Seek_wrapped(_Dest, _UDest); + + return _Dest; +} + +template +_CONSTEXPR20 _BidIt2 copy_backward(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest) { + // copy [_First, _Last) backwards to [..., _Dest) + _Adl_verify_range(_First, _Last); + const auto _UFirst = _Get_unwrapped(_First); + const auto _ULast = _Get_unwrapped(_Last); + const auto _UDest = _Get_unwrapped_n(_Dest, -_Idl_distance<_BidIt1>(_UFirst, _ULast)); + _Seek_wrapped(_Dest, _Copy_backward_unchecked(_UFirst, _ULast, _UDest)); return _Dest; } #else // ^^^ _HAS_IF_CONSTEXPR / !_HAS_IF_CONSTEXPR vvv diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 860b9360d40..61292ad030b 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -346,7 +346,7 @@ #define _MSVC_KNOWN_SEMANTICS #endif -// Controls whether the STL uses "if constexpr" internally +// Controls whether the STL uses "if constexpr" internally in C++14 mode #ifndef _HAS_IF_CONSTEXPR #ifdef __CUDACC__ #define _HAS_IF_CONSTEXPR 0 diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index 58febafc5fc..1a6b09496eb 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -125,6 +125,26 @@ namespace test { ptr_ = s.peek(); } // clang-format on + + [[nodiscard]] friend constexpr boolish operator==(sentinel const s, Element* const ptr) noexcept { + return {s.ptr_ == ptr}; + } + [[nodiscard]] friend constexpr boolish operator==(Element* const ptr, sentinel const s) noexcept { + return {s.ptr_ == ptr}; + } + [[nodiscard]] friend constexpr boolish operator!=(sentinel const s, Element* const ptr) noexcept { + return !(s == ptr); + } + [[nodiscard]] friend constexpr boolish operator!=(Element* const ptr, sentinel const s) noexcept { + return !(s == ptr); + } + + [[nodiscard]] friend constexpr ptrdiff_t operator-(sentinel const s, Element* const ptr) noexcept { + return s.ptr_ - ptr; + } + [[nodiscard]] friend constexpr ptrdiff_t operator-(Element* const ptr, sentinel const s) noexcept { + return ptr - s.ptr_; + } }; // clang-format off @@ -188,70 +208,86 @@ namespace test { // clang-format on template - constexpr boolish operator==(proxy_reference that) const requires CanEq { + [[nodiscard]] constexpr boolish operator==(proxy_reference that) const + requires CanEq { return {ref_ == that.peek()}; } template - constexpr boolish operator!=(proxy_reference that) const requires CanNEq { + [[nodiscard]] constexpr boolish operator!=(proxy_reference that) const + requires CanNEq { return {ref_ != that.peek()}; } template - constexpr boolish operator<(proxy_reference that) const requires CanLt { + [[nodiscard]] constexpr boolish operator<(proxy_reference that) const requires CanLt { return {ref_ < that.peek()}; } template - constexpr boolish operator>(proxy_reference that) const requires CanGt { + [[nodiscard]] constexpr boolish operator>(proxy_reference that) const requires CanGt { return {ref_ > that.peek()}; } template - constexpr boolish operator<=(proxy_reference that) const requires CanLtE { + [[nodiscard]] constexpr boolish operator<=(proxy_reference that) const + requires CanLtE { return {ref_ <= that.peek()}; } template - constexpr boolish operator>=(proxy_reference that) const requires CanGtE { + [[nodiscard]] constexpr boolish operator>=(proxy_reference that) const + requires CanGtE { return {ref_ >= that.peek()}; } // clang-format off - friend constexpr boolish operator==(proxy_reference r, Value const& val) requires CanEq { + [[nodiscard]] friend constexpr boolish operator==(proxy_reference r, Value const& val) + requires CanEq { return {r.ref_ == val}; } - friend constexpr boolish operator==(Value const& val, proxy_reference r) requires CanEq { + [[nodiscard]] friend constexpr boolish operator==(Value const& val, proxy_reference r) + requires CanEq { return {r.ref_ == val}; } - friend constexpr boolish operator!=(proxy_reference r, Value const& val) requires CanNEq { + [[nodiscard]] friend constexpr boolish operator!=(proxy_reference r, Value const& val) + requires CanNEq { return {r.ref_ != val}; } - friend constexpr boolish operator!=(Value const& val, proxy_reference r) requires CanNEq { + [[nodiscard]] friend constexpr boolish operator!=(Value const& val, proxy_reference r) + requires CanNEq { return {r.ref_ != val}; } - friend constexpr boolish operator<(Value const& val, proxy_reference r) requires CanLt { + [[nodiscard]] friend constexpr boolish operator<(Value const& val, proxy_reference r) + requires CanLt { return {val < r.ref_}; } - friend constexpr boolish operator<(proxy_reference r, Value const& val) requires CanLt { + [[nodiscard]] friend constexpr boolish operator<(proxy_reference r, Value const& val) + requires CanLt { return {r.ref_ < val}; } - friend constexpr boolish operator>(Value const& val, proxy_reference r) requires CanGt { + [[nodiscard]] friend constexpr boolish operator>(Value const& val, proxy_reference r) + requires CanGt { return {val > r.ref_}; } - friend constexpr boolish operator>(proxy_reference r, Value const& val) requires CanGt { + [[nodiscard]] friend constexpr boolish operator>(proxy_reference r, Value const& val) + requires CanGt { return {r.ref_ > val}; } - friend constexpr boolish operator<=(Value const& val, proxy_reference r) requires CanLtE { + [[nodiscard]] friend constexpr boolish operator<=(Value const& val, proxy_reference r) + requires CanLtE { return {val <= r.ref_}; } - friend constexpr boolish operator<=(proxy_reference r, Value const& val) requires CanLtE { + [[nodiscard]] friend constexpr boolish operator<=(proxy_reference r, Value const& val) + requires CanLtE { return {r.ref_ <= val}; } - friend constexpr boolish operator>=(Value const& val, proxy_reference r) requires CanGtE { + [[nodiscard]] friend constexpr boolish operator>=(Value const& val, proxy_reference r) + requires CanGtE { return {val >= r.ref_}; } - friend constexpr boolish operator>=(proxy_reference r, Value const& val) requires CanGtE { + [[nodiscard]] friend constexpr boolish operator>=(proxy_reference r, Value const& val) + requires CanGtE { return {r.ref_ >= val}; } // clang-format on - constexpr Element& peek() const noexcept { + [[nodiscard]] constexpr Element& peek() const noexcept { return ref_; } }; @@ -466,7 +502,8 @@ namespace test { // iterator unwrapping operations: using _Prevent_inheriting_unwrap = iterator; - using unwrap = iterator; + using unwrap = std::conditional_t, Element*, + iterator>; [[nodiscard]] constexpr auto _Unwrapped() const& noexcept requires (to_bool(Wrapped) && to_bool(Eq)) { return unwrap{ptr_}; @@ -479,11 +516,19 @@ namespace test { static constexpr bool _Unwrap_when_unverified = true; constexpr void _Seek_to(unwrap const& i) noexcept requires (to_bool(Wrapped) && to_bool(Eq)) { - ptr_ = i.peek(); + if constexpr (at_least) { + ptr_ = i; + } else { + ptr_ = i.peek(); + } } constexpr void _Seek_to(unwrap&& i) noexcept requires (to_bool(Wrapped)) { - ptr_ = i.peek(); + if constexpr (at_least) { + ptr_ = i; + } else { + ptr_ = i.peek(); + } } }; // clang-format on @@ -570,8 +615,8 @@ namespace test { return elements_.data(); } - using UI = iterator; - using US = conditional_t>; + using UI = typename I::unwrap; + using US = conditional_t>; [[nodiscard]] constexpr UI _Unchecked_begin() const noexcept { if constexpr (!derived_from) { @@ -1003,6 +1048,11 @@ constexpr void test_fwd_fwd() { with_forward_ranges, Element1>::call(); } +template +constexpr void test_bidi_bidi() { + with_bidirectional_ranges, Element1>::call(); +} + template constexpr void test_in_write() { with_input_ranges, Element1>::call(); diff --git a/tests/std/test.lst b/tests/std/test.lst index db59922818f..56b6f4f7191 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -241,6 +241,7 @@ tests\P0896R4_ranges_alg_all_of tests\P0896R4_ranges_alg_any_of tests\P0896R4_ranges_alg_binary_search tests\P0896R4_ranges_alg_copy +tests\P0896R4_ranges_alg_copy_backward tests\P0896R4_ranges_alg_copy_if tests\P0896R4_ranges_alg_copy_n tests\P0896R4_ranges_alg_count diff --git a/tests/std/tests/P0896R4_ranges_alg_copy_backward/env.lst b/tests/std/tests/P0896R4_ranges_alg_copy_backward/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_copy_backward/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_copy_backward/test.cpp b/tests/std/tests/P0896R4_ranges_alg_copy_backward/test.cpp new file mode 100644 index 00000000000..ac89778f756 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_copy_backward/test.cpp @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include + +using namespace std; + +// Validate that copy_backward_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, static_cast(nullptr))), + ranges::copy_backward_result>); +STATIC_ASSERT(same_as{}, static_cast(nullptr))), + ranges::copy_backward_result>); + +static constexpr int input[] = {13, 42, 1729}; +static constexpr int expected_overlapping[] = {0, 0, 1, 2}; + +struct instantiator { + template + static constexpr void call() { + // For the second range, we need an iterator to the end; it's expedient to simply ignore ranges with differing + // iterator and sentinel types (i.e., ranges that don't satisfy common_range). + if constexpr (ranges::common_range) { + using ranges::copy_backward, ranges::copy_backward_result, ranges::equal, ranges::iterator_t; + const Bidi1 wrapped_input{input}; + + { // Validate range overload + int output[] = {-2, -2, -2}; + Bidi2 wrapped_output{output}; + auto result = copy_backward(wrapped_input, wrapped_output.end()); + STATIC_ASSERT(same_as, iterator_t>>); + assert(result.in == wrapped_input.end()); + assert(result.out == wrapped_output.begin()); + assert(equal(output, input)); + } + { // Validate iterator + sentinel overload + int output[] = {-2, -2, -2}; + Bidi2 wrapped_output{output}; + auto result = copy_backward(wrapped_input.begin(), wrapped_input.end(), wrapped_output.end()); + STATIC_ASSERT(same_as, iterator_t>>); + assert(result.in == wrapped_input.end()); + assert(result.out == wrapped_output.begin()); + assert(equal(output, input)); + } + { // Validate overlapping ranges + int io[] = {0, 1, 2, 42}; + Bidi1 in{span{io}.first<3>()}; + Bidi2 out{span{io}.last<3>()}; + auto result = copy_backward(in, out.end()); + STATIC_ASSERT(same_as, iterator_t>>); + assert(result.in == in.end()); + assert(result.out == out.begin()); + assert(equal(io, expected_overlapping)); + } + } + } +}; + +constexpr void test_memmove() { + // Get some coverage for the memmove optimization, which we would not otherwise have since we do not currently + // unwrap output iterators. TRANSITION, GH-893 + using ranges::copy_backward, ranges::copy_backward_result, ranges::begin, ranges::end, ranges::equal; + + { // Validate range overload + int output[] = {-2, -2, -2}; + auto result = copy_backward(input, end(output)); + STATIC_ASSERT(same_as>); + assert(result.in == end(input)); + assert(result.out == begin(output)); + assert(equal(output, input)); + } + { // Validate iterator + sentinel overload + int output[] = {-2, -2, -2}; + auto result = copy_backward(begin(input), end(input), end(output)); + STATIC_ASSERT(same_as>); + assert(result.in == end(input)); + assert(result.out == begin(output)); + assert(equal(output, input)); + } + { // Validate overlapping ranges + int io[] = {0, 1, 2, 42}; + auto result = copy_backward(io + 0, io + 3, io + 4); + STATIC_ASSERT(same_as>); + assert(result.in == io + 3); + assert(result.out == io + 1); + assert(equal(io, expected_overlapping)); + } +} + +int main() { + STATIC_ASSERT((test_bidi_bidi(), true)); + test_bidi_bidi(); + + STATIC_ASSERT((test_memmove(), true)); + test_memmove(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp index 95795d1ab9a..cd30e073d21 100644 --- a/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp @@ -101,6 +101,8 @@ struct instantiator { }; int main() { +#ifndef _PREFAST_ // TRANSITION, GH-1030 STATIC_ASSERT((test_in_write(), true)); +#endif // TRANSITION, GH-1030 test_in_write(); } diff --git a/tests/std/tests/P0896R4_ranges_alg_heap/test.cpp b/tests/std/tests/P0896R4_ranges_alg_heap/test.cpp index ac24c5645f2..d938ff02434 100644 --- a/tests/std/tests/P0896R4_ranges_alg_heap/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_heap/test.cpp @@ -108,11 +108,16 @@ struct make_and_sort_heap_test { ASSERT(!is_heap(wrapped, less{}, get_first)); - make_heap(wrapped, less{}, get_first); - ASSERT(is_heap(wrapped, less{}, get_first)); - - sort_heap(wrapped, less{}, get_first); - ASSERT(is_sorted(wrapped, less{}, get_first)); +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 + if constexpr (!ranges::contiguous_range) +#endif // TRANSITION, VSO-938163 + { + make_heap(wrapped, less{}, get_first); + ASSERT(is_heap(wrapped, less{}, get_first)); + + sort_heap(wrapped, less{}, get_first); + ASSERT(is_sorted(wrapped, less{}, get_first)); + } } { @@ -121,11 +126,16 @@ struct make_and_sort_heap_test { ASSERT(!is_heap(wrapped.begin(), wrapped.end(), less{}, get_first)); - make_heap(wrapped.begin(), wrapped.end(), less{}, get_first); - ASSERT(is_heap(wrapped.begin(), wrapped.end(), less{}, get_first)); +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 + if constexpr (!ranges::contiguous_range) +#endif // TRANSITION, VSO-938163 + { + make_heap(wrapped.begin(), wrapped.end(), less{}, get_first); + ASSERT(is_heap(wrapped.begin(), wrapped.end(), less{}, get_first)); - sort_heap(wrapped.begin(), wrapped.end(), less{}, get_first); - ASSERT(is_sorted(wrapped.begin(), wrapped.end(), less{}, get_first)); + sort_heap(wrapped.begin(), wrapped.end(), less{}, get_first); + ASSERT(is_sorted(wrapped.begin(), wrapped.end(), less{}, get_first)); + } } } }; @@ -182,24 +192,34 @@ struct push_and_pop_heap_test { auto buff = initial_values; const Range wrapped{buff}; - pop_heap(wrapped, less{}, get_first); - ASSERT(equal(wrapped, expectedPopped)); +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 + if constexpr (!ranges::contiguous_range) +#endif // TRANSITION, VSO-938163 + { + pop_heap(wrapped, less{}, get_first); + ASSERT(equal(wrapped, expectedPopped)); - push_heap(wrapped, less{}, get_first); - ASSERT(equal(wrapped, expectedPushed)); + push_heap(wrapped, less{}, get_first); + ASSERT(equal(wrapped, expectedPushed)); + } } { auto buff = initial_values; const Range wrapped{buff}; - pop_heap(wrapped.begin(), wrapped.end(), less{}, get_first); - ASSERT(is_heap(expectedPopped.begin(), expectedPopped.end() - 1, less{}, get_first)); - ASSERT(equal(wrapped.begin(), wrapped.end(), expectedPopped.begin(), expectedPopped.end())); - - push_heap(wrapped.begin(), wrapped.end(), less{}, get_first); - ASSERT(is_heap(expectedPushed, less{}, get_first)); - ASSERT(equal(wrapped.begin(), wrapped.end(), expectedPushed.begin(), expectedPushed.end())); +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 + if constexpr (!ranges::contiguous_range) +#endif // TRANSITION, VSO-938163 + { + pop_heap(wrapped.begin(), wrapped.end(), less{}, get_first); + ASSERT(is_heap(expectedPopped.begin(), expectedPopped.end() - 1, less{}, get_first)); + ASSERT(equal(wrapped.begin(), wrapped.end(), expectedPopped.begin(), expectedPopped.end())); + + push_heap(wrapped.begin(), wrapped.end(), less{}, get_first); + ASSERT(is_heap(expectedPushed, less{}, get_first)); + ASSERT(equal(wrapped.begin(), wrapped.end(), expectedPushed.begin(), expectedPushed.end())); + } } } }; diff --git a/tests/std/tests/P0896R4_ranges_alg_mismatch/test.cpp b/tests/std/tests/P0896R4_ranges_alg_mismatch/test.cpp index 30f7c5e8394..51a0b864436 100644 --- a/tests/std/tests/P0896R4_ranges_alg_mismatch/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_mismatch/test.cpp @@ -71,7 +71,9 @@ constexpr void smoke_test() { } int main() { +#ifndef _PREFAST_ // TRANSITION, GH-1030 STATIC_ASSERT((smoke_test(), true)); +#endif // TRANSITION, GH-1030 smoke_test(); } diff --git a/tests/std/tests/P0896R4_ranges_alg_move/test.cpp b/tests/std/tests/P0896R4_ranges_alg_move/test.cpp index 34c485670ef..dbd4e70a55f 100644 --- a/tests/std/tests/P0896R4_ranges_alg_move/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_move/test.cpp @@ -35,40 +35,45 @@ struct instantiator { static constexpr int_wrapper expected_output[3] = {13, 55, 12345}; static constexpr int_wrapper expected_input[3] = {-1, -1, -1}; - static constexpr void eq(int_wrapper const (&output)[3], int_wrapper const (&input)[3]) { - // Extracted into a separate function to keep /analyze from exhausting the compiler heap - assert(ranges::equal(output, expected_output)); - assert(ranges::equal(input, expected_input)); - } - template > Write> static constexpr void call() { - using ranges::move, ranges::move_result, ranges::iterator_t; +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 +#pragma warning(suppress : 4127) // conditional expression is constant + if (!ranges::contiguous_range || !is_constant_evaluated()) +#endif // TRANSITION, VSO-938163 { - int_wrapper input[3] = {13, 55, 12345}; - int_wrapper output[3] = {-2, -2, -2}; - Read wrapped_input{input}; + using ranges::move, ranges::move_result, ranges::equal, ranges::iterator_t; + { + int_wrapper input[3] = {13, 55, 12345}; + int_wrapper output[3] = {-2, -2, -2}; + Read wrapped_input{input}; - auto result = move(wrapped_input, Write{output}); - STATIC_ASSERT(same_as, Write>>); - assert(result.in == wrapped_input.end()); - assert(result.out.peek() == output + 3); - eq(output, input); - } - { - int_wrapper input[3] = {13, 55, 12345}; - int_wrapper output[3] = {-2, -2, -2}; - Read wrapped_input{input}; + auto result = move(wrapped_input, Write{output}); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == output + 3); + assert(equal(output, expected_output)); + assert(equal(input, expected_input)); + } + { + int_wrapper input[3] = {13, 55, 12345}; + int_wrapper output[3] = {-2, -2, -2}; + Read wrapped_input{input}; - auto result = move(wrapped_input.begin(), wrapped_input.end(), Write{output}); - assert(result.in == wrapped_input.end()); - assert(result.out.peek() == output + 3); - eq(output, input); + auto result = move(wrapped_input.begin(), wrapped_input.end(), Write{output}); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == output + 3); + assert(equal(output, expected_output)); + assert(equal(input, expected_input)); + } } } }; int main() { +#ifndef _PREFAST_ // TRANSITION, GH-1030 STATIC_ASSERT((test_in_write(), true)); +#endif // TRANSITION, GH-1030 test_in_write(); } diff --git a/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp index b020fbcd8c2..6d07fb598f1 100644 --- a/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp @@ -24,14 +24,9 @@ struct instantiator { static constexpr P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; static constexpr P expected[5] = {{0, 99}, {47, 1}, {2, 99}, {47, 1}, {4, 99}}; - static constexpr void eq(P const (&output)[5]) { - // Extracted into a separate function to keep /analyze from exhausting the compiler heap - assert(ranges::equal(output, expected)); - } - template > Write> static constexpr void call() { - using ranges::replace_copy, ranges::replace_copy_result, ranges::iterator_t; + using ranges::replace_copy, ranges::replace_copy_result, ranges::equal, ranges::iterator_t; { // Validate iterator + sentinel overload P output[5] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; Read wrapped_input{input}; @@ -41,7 +36,7 @@ struct instantiator { STATIC_ASSERT(same_as, Write>>); assert(result.in == wrapped_input.end()); assert(result.out.peek() == output + 5); - eq(output); + assert(equal(output, expected)); } { // Validate range overload P output[5] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; @@ -51,12 +46,14 @@ struct instantiator { STATIC_ASSERT(same_as, Write>>); assert(result.in == wrapped_input.end()); assert(result.out.peek() == output + 5); - eq(output); + assert(equal(output, expected)); } } }; int main() { +#ifndef _PREFAST_ // TRANSITION, GH-1030 STATIC_ASSERT((input_range_output_iterator_permutations(), true)); +#endif // TRANSITION, GH-1030 input_range_output_iterator_permutations(); } diff --git a/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp index 57afc1c7226..76eba760270 100644 --- a/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp @@ -26,14 +26,9 @@ struct instantiator { static constexpr P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; static constexpr P expected[5] = {{0, 99}, {47, 1}, {2, 99}, {47, 1}, {4, 99}}; - static constexpr void eq(P const (&output)[5]) { - // Extracted into a separate function to keep /analyze from exhausting the compiler heap - assert(ranges::equal(output, expected)); - } - template > Write> static constexpr void call() { - using ranges::replace_copy_if, ranges::replace_copy_if_result, ranges::iterator_t; + using ranges::replace_copy_if, ranges::replace_copy_if_result, ranges::equal, ranges::iterator_t; { // Validate iterator + sentinel overload P output[5] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; Read wrapped_input{input}; @@ -43,7 +38,7 @@ struct instantiator { STATIC_ASSERT(same_as, Write>>); assert(result.in == wrapped_input.end()); assert(result.out.peek() == output + 5); - eq(output); + assert(equal(output, expected)); } { // Validate range overload P output[5] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; @@ -53,12 +48,14 @@ struct instantiator { STATIC_ASSERT(same_as, Write>>); assert(result.in == wrapped_input.end()); assert(result.out.peek() == output + 5); - eq(output); + assert(equal(output, expected)); } } }; int main() { +#ifndef _PREFAST_ // TRANSITION, GH-1030 STATIC_ASSERT((input_range_output_iterator_permutations(), true)); +#endif // TRANSITION, GH-1030 input_range_output_iterator_permutations(); } diff --git a/tests/std/tests/P0896R4_ranges_test_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_test_machinery/test.cpp index 29235e1ecaa..ce114438a6f 100644 --- a/tests/std/tests/P0896R4_ranges_test_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_test_machinery/test.cpp @@ -26,9 +26,8 @@ constexpr bool iter_test() { STATIC_ASSERT(!movable || indirectly_writable); - constexpr bool can_write = - derived_from || (derived_from && assignable_from); + constexpr bool can_write = derived_from // + || (derived_from && assignable_from); STATIC_ASSERT(!can_write || output_iterator); STATIC_ASSERT(!derived_from || input_iterator); @@ -44,7 +43,11 @@ constexpr bool iter_test() { STATIC_ASSERT(!to_bool(Eq) || !to_bool(Diff) || sized_sentinel_for); if constexpr (to_bool(Wrapped)) { - STATIC_ASSERT(same_as<_Unwrapped_t, iterator>); + if constexpr (derived_from) { + STATIC_ASSERT(same_as<_Unwrapped_t, Element*>); + } else { + STATIC_ASSERT(same_as<_Unwrapped_t, iterator>); + } STATIC_ASSERT(same_as<_Unwrapped_t, sentinel>); } diff --git a/tests/std/tests/P0898R3_concepts/test.cpp b/tests/std/tests/P0898R3_concepts/test.cpp index 890f0381d2b..80c224e7df3 100644 --- a/tests/std/tests/P0898R3_concepts/test.cpp +++ b/tests/std/tests/P0898R3_concepts/test.cpp @@ -2407,7 +2407,8 @@ namespace test_boolean_testable { struct Archetype { // clang-format off operator bool() const requires (Select != 0); // Archetype<0> is not implicitly convertible to bool - explicit operator bool() const requires (Select < 2); // Archetype<1> is not explicitly convertible to bool (ambiguity) + explicit operator bool() const requires (Select < 2); // Archetype<1> is not explicitly convertible + // to bool (ambiguity) void operator!() const requires (Select == 2); // !Archetype<2> does not model _Boolean_testable_impl // clang-format on }; diff --git a/tests/std/tests/concepts_matrix.lst b/tests/std/tests/concepts_matrix.lst index 37b0a2358be..8414e31b200 100644 --- a/tests/std/tests/concepts_matrix.lst +++ b/tests/std/tests/concepts_matrix.lst @@ -12,12 +12,12 @@ PM_CL="/MDd /D_ITERATOR_DEBUG_LEVEL=0 /permissive- /Zc:wchar_t-" PM_CL="/MDd /D_ITERATOR_DEBUG_LEVEL=1" PM_CL="/MDd /D_ITERATOR_DEBUG_LEVEL=2 /fp:except /Zc:preprocessor" PM_CL="/MT /D_ITERATOR_DEBUG_LEVEL=0 /await" -# PM_CL="/MT /D_ITERATOR_DEBUG_LEVEL=0 /analyze:only" # TRANSITION, GH-1030 +PM_CL="/MT /D_ITERATOR_DEBUG_LEVEL=0 /analyze:only" PM_CL="/MT /D_ITERATOR_DEBUG_LEVEL=1" PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=0 /fp:strict" PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=1" PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=2 /await" -# PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=2 /analyze:only" # TRANSITION, GH-1030 +PM_CL="/MTd /D_ITERATOR_DEBUG_LEVEL=2 /analyze:only" PM_CL="/Za /MD /permissive-" PM_CL="/Za /MDd /permissive-" # PM_CL="/BE /c /MD" diff --git a/tests/tr1/include/tspec_random.h b/tests/tr1/include/tspec_random.h index 64c6e6c4b6b..9580a50b323 100644 --- a/tests/tr1/include/tspec_random.h +++ b/tests/tr1/include/tspec_random.h @@ -8,11 +8,12 @@ using namespace std; // COMMON TYPES #define FLIT(x) x -#define TESTR(fun) do_random_test::gen_data(fun##_dist, fun##_vals, fun##_smaller_ok, fun##_larger_ok) #define BINSIZE 100 #define SAMPLESIZE 100000 #define CHI_SQUARED_FAIL_LIMIT 125.0 +#define TESTR(fun) do_random_test::gen_data(fun##_dist, fun##_vals, fun##_smaller_ok, fun##_larger_ok) + typedef struct One_arg { // argument plus return value Float_type arg1; Float_type ans; From 4f949afa0e8c96b61792f8bb6cb38cb086a0b037 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 20 Jul 2020 16:45:04 -0700 Subject: [PATCH 05/50] Implement ranges::reverse (#1028) Co-authored-by: Ahana Mukhopadhyay --- stl/inc/algorithm | 71 +++++++ tests/std/test.lst | 1 + .../tests/P0896R4_ranges_alg_reverse/env.lst | 4 + .../tests/P0896R4_ranges_alg_reverse/test.cpp | 183 ++++++++++++++++++ 4 files changed, 259 insertions(+) create mode 100644 tests/std/tests/P0896R4_ranges_alg_reverse/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_reverse/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 80a0ce54552..9f5ea0f97cf 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -4505,6 +4505,77 @@ _FwdIt2 unique_copy(_ExPo&&, _FwdIt1 _First, _FwdIt1 _Last, _FwdIt2 _Dest) noexc } #endif // _HAS_CXX17 +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::reverse + // clang-format off + // concept-constrained for strict enforcement as it is used by several algorithms + template + requires permutable<_It> + constexpr void _Reverse_common(_It _First, _It _Last) { +#if _USE_STD_VECTOR_ALGORITHMS + if constexpr (contiguous_iterator<_It>) { + using _Elem = remove_reference_t>; + constexpr size_t _Nx = sizeof(_Elem); + constexpr bool _Allow_vectorization = + conjunction_v<_Is_trivially_swappable<_Elem>, negation>>; + + if constexpr (_Allow_vectorization && _Nx <= 8 && (_Nx & (_Nx - 1)) == 0) { + if (!_STD is_constant_evaluated()) { + _Elem* const _First_addr = _STD to_address(_First); + _Elem* const _Last_addr = _STD to_address(_Last); + if constexpr (_Nx == 1) { + __std_reverse_trivially_swappable_1(_First_addr, _Last_addr); + } else if constexpr (_Nx == 2) { + __std_reverse_trivially_swappable_2(_First_addr, _Last_addr); + } else if constexpr (_Nx == 4) { + __std_reverse_trivially_swappable_4(_First_addr, _Last_addr); + } else { + __std_reverse_trivially_swappable_8(_First_addr, _Last_addr); + } + + return; + } + } + } +#endif // _USE_STD_VECTOR_ALGORITHMS + + for (; _First != _Last && _First != --_Last; ++_First) { + _RANGES iter_swap(_First, _Last); + } + } + // clang-format on + + class _Reverse_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se> + requires permutable<_It> + constexpr _It operator()(_It _First, _Se _Last) const { + _Adl_verify_range(_First, _Last); + auto _UFirst = _Get_unwrapped(_STD move(_First)); + auto _ULast = _Get_final_iterator_unwrapped<_It>(_UFirst, _STD move(_Last)); + _Seek_wrapped(_First, _ULast); + _RANGES _Reverse_common(_STD move(_UFirst), _STD move(_ULast)); + return _First; + } + + template + requires permutable> + constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range) const { + auto _ULast = _Get_final_iterator_unwrapped(_Range); + _RANGES _Reverse_common(_Ubegin(_Range), _ULast); + return _Rewrap_iterator(_Range, _STD move(_ULast)); + } + // clang-format on + }; + + inline constexpr _Reverse_fn reverse{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE reverse_copy template _CONSTEXPR20 _OutIt reverse_copy(_BidIt _First, _BidIt _Last, _OutIt _Dest) { diff --git a/tests/std/test.lst b/tests/std/test.lst index 56b6f4f7191..8b46ac33bfa 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -276,6 +276,7 @@ tests\P0896R4_ranges_alg_replace tests\P0896R4_ranges_alg_replace_copy tests\P0896R4_ranges_alg_replace_copy_if tests\P0896R4_ranges_alg_replace_if +tests\P0896R4_ranges_alg_reverse tests\P0896R4_ranges_alg_search tests\P0896R4_ranges_alg_search_n tests\P0896R4_ranges_alg_swap_ranges diff --git a/tests/std/tests/P0896R4_ranges_alg_reverse/env.lst b/tests/std/tests/P0896R4_ranges_alg_reverse/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_reverse/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_reverse/test.cpp b/tests/std/tests/P0896R4_ranges_alg_reverse/test.cpp new file mode 100644 index 00000000000..53d0251a472 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_reverse/test.cpp @@ -0,0 +1,183 @@ +// 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 dangling story +STATIC_ASSERT(same_as{})), ranges::dangling>); +STATIC_ASSERT(same_as{})), int*>); + +struct nontrivial_int { + int val; + + constexpr nontrivial_int(int i) noexcept : val{i} {} + constexpr nontrivial_int(const nontrivial_int& that) noexcept : val{that.val} {} + constexpr nontrivial_int& operator=(const nontrivial_int& that) noexcept { + val = that.val; + return *this; + } + + auto operator<=>(const nontrivial_int&) const = default; +}; + +struct instantiator { + static constexpr nontrivial_int expected_odd[] = {1367, 42, 13}; + static constexpr nontrivial_int expected_even[] = {1729, 1367, 42, 13}; + + template + static constexpr void call() { + using ranges::reverse, ranges::equal, ranges::iterator_t; + + { // Validate iterator + sentinel overload, odd length + nontrivial_int input[] = {13, 42, 1367}; + R wrapped_input{input}; + auto result = reverse(wrapped_input.begin(), wrapped_input.end()); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + assert(equal(input, expected_odd)); + } + { // Validate range overload, odd length + nontrivial_int input[] = {13, 42, 1367}; + R wrapped_input{input}; + auto result = reverse(wrapped_input); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + assert(equal(input, expected_odd)); + } + { // Validate iterator + sentinel overload, even length + nontrivial_int input[] = {13, 42, 1367, 1729}; + R wrapped_input{input}; + auto result = reverse(wrapped_input.begin(), wrapped_input.end()); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + assert(equal(input, expected_even)); + } + { // Validate range overload, even length + nontrivial_int input[] = {13, 42, 1367, 1729}; + R wrapped_input{input}; + auto result = reverse(wrapped_input); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + assert(equal(input, expected_even)); + } + { // Validate iterator + sentinel overload, empty range + R wrapped_input{}; + auto result = reverse(wrapped_input.begin(), wrapped_input.end()); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + } + { // Validate range overload, empty range + R wrapped_input{}; + auto result = reverse(wrapped_input); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + } + } +}; + +template +struct bytes { + unsigned char storage[N]; + + constexpr bytes(unsigned char base) { + iota(storage, storage + N, base); + } + + bool operator==(const bytes&) const = default; +}; + +struct test_vector { + template + static constexpr void call() { + using ranges::reverse, ranges::equal, ranges::iterator_t; + + { // Validate iterator + sentinel overload, vectorizable odd length + ranges::range_value_t input[]{0x10, 0x20, 0x30}; + R wrapped_input{input}; + auto result = reverse(wrapped_input.begin(), wrapped_input.end()); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + assert(equal(input, initializer_list>{0x30, 0x20, 0x10})); + } + { // Validate range overload, vectorizable odd length + ranges::range_value_t input[]{0x10, 0x20, 0x30}; + R wrapped_input{input}; + auto result = reverse(wrapped_input); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + assert(equal(input, initializer_list>{0x30, 0x20, 0x10})); + } + + { // Validate iterator + sentinel overload, vectorizable even length + ranges::range_value_t input[]{0x10, 0x20, 0x30, 0x40}; + R wrapped_input{input}; + auto result = reverse(wrapped_input.begin(), wrapped_input.end()); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + assert(equal(input, initializer_list>{0x40, 0x30, 0x20, 0x10})); + } + { // Validate range overload, vectorizable even length + ranges::range_value_t input[]{0x10, 0x20, 0x30, 0x40}; + R wrapped_input{input}; + auto result = reverse(wrapped_input); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + assert(equal(input, initializer_list>{0x40, 0x30, 0x20, 0x10})); + } + + { // Validate iterator + sentinel overload, vectorizable empty + R wrapped_input{}; + auto result = reverse(wrapped_input.begin(), wrapped_input.end()); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + } + { // Validate range overload, vectorizable empty + R wrapped_input{}; + auto result = reverse(wrapped_input); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + } + } +}; + +int main() { +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-938163 + STATIC_ASSERT((test_bidi(), true)); +#endif // TRANSITION, VSO-938163 + test_bidi(); + +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-938163 + STATIC_ASSERT((test_contiguous>(), true)); +#endif // TRANSITION, VSO-938163 + test_contiguous>(); + +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-938163 + STATIC_ASSERT((test_contiguous>(), true)); +#endif // TRANSITION, VSO-938163 + test_contiguous>(); + +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-938163 + STATIC_ASSERT((test_contiguous>(), true)); +#endif // TRANSITION, VSO-938163 + test_contiguous>(); + +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-938163 + STATIC_ASSERT((test_contiguous>(), true)); +#endif // TRANSITION, VSO-938163 + test_contiguous>(); + +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-938163 + STATIC_ASSERT((test_contiguous>(), true)); +#endif // TRANSITION, VSO-938163 + test_contiguous>(); +} From 7930d3512f74b3263efc315a06551d406ccd3191 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 20 Jul 2020 16:54:04 -0700 Subject: [PATCH 06/50] : Fix 0 <=> partial_ordering::unordered (#1049) * Break ABI again, and fix #1050. * Remove obsolete conditional compilation from . Co-authored-by: statementreply Co-authored-by: Stephan T. Lavavej --- stl/inc/compare | 98 ++++----------- stl/inc/span | 30 +---- stl/inc/xutility | 31 ++--- .../tests/P0768R1_spaceship_operator/test.cpp | 113 ++++++++++-------- 4 files changed, 100 insertions(+), 172 deletions(-) diff --git a/stl/inc/compare b/stl/inc/compare index 389b5b58aa5..717e7b4b789 100644 --- a/stl/inc/compare +++ b/stl/inc/compare @@ -32,7 +32,7 @@ using _Compare_t = signed char; // These "pretty" enumerator names are safe since they reuse names of user-facing entities. enum class _Compare_eq : _Compare_t { equal = 0, equivalent = equal }; enum class _Compare_ord : _Compare_t { less = -1, greater = 1 }; -enum class _Compare_ncmp : _Compare_t { unordered = -127 }; +enum class _Compare_ncmp : _Compare_t { unordered = -128 }; // CLASS partial_ordering class partial_ordering { @@ -53,21 +53,7 @@ public: return _Val._Value == 0; } -#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L - _NODISCARD friend constexpr bool operator==(const partial_ordering&, const partial_ordering&) noexcept = default; -#else // ^^^ supports <=> and P1185 / supports neither vvv - _NODISCARD friend constexpr bool operator!=(const partial_ordering _Val, _Literal_zero) noexcept { - return _Val._Value != 0; - } - - _NODISCARD friend constexpr bool operator==(_Literal_zero, const partial_ordering _Val) noexcept { - return 0 == _Val._Value; - } - - _NODISCARD friend constexpr bool operator!=(_Literal_zero, const partial_ordering _Val) noexcept { - return 0 != _Val._Value; - } -#endif // defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L + _NODISCARD friend constexpr bool operator==(partial_ordering, partial_ordering) noexcept = default; _NODISCARD friend constexpr bool operator<(const partial_ordering _Val, _Literal_zero) noexcept { return _Val._Value == static_cast<_Compare_t>(_Compare_ord::less); @@ -78,7 +64,10 @@ public: } _NODISCARD friend constexpr bool operator<=(const partial_ordering _Val, _Literal_zero) noexcept { - return _Val._Value <= 0 && _Val._Is_ordered(); + // The stored value is either less (0xff), equivalent (0x00), greater (0x01), or unordered (0x80). + // Subtracting from 0 produces either 0x01, 0x00, 0xff, or 0x80. The result is greater than or equal to 0 + // if and only if the initial value was less or equivalent, for which we want to return true. + return static_cast(0 - static_cast(_Val._Value)) >= 0; } _NODISCARD friend constexpr bool operator>=(const partial_ordering _Val, _Literal_zero) noexcept { @@ -86,36 +75,33 @@ public: } _NODISCARD friend constexpr bool operator<(_Literal_zero, const partial_ordering _Val) noexcept { - return 0 < _Val._Value; + return _Val > 0; } _NODISCARD friend constexpr bool operator>(_Literal_zero, const partial_ordering _Val) noexcept { - return 0 > _Val._Value && _Val._Is_ordered(); + return _Val < 0; } _NODISCARD friend constexpr bool operator<=(_Literal_zero, const partial_ordering _Val) noexcept { - return 0 <= _Val._Value; + return _Val >= 0; } _NODISCARD friend constexpr bool operator>=(_Literal_zero, const partial_ordering _Val) noexcept { - return 0 >= _Val._Value && _Val._Is_ordered(); + return _Val <= 0; } -#ifdef __cpp_impl_three_way_comparison _NODISCARD friend constexpr partial_ordering operator<=>(const partial_ordering _Val, _Literal_zero) noexcept { return _Val; } _NODISCARD friend constexpr partial_ordering operator<=>(_Literal_zero, const partial_ordering _Val) noexcept { - return partial_ordering{static_cast<_Compare_ord>(-_Val._Value)}; + // The stored value is either less (0xff), equivalent (0x00), greater (0x01), or unordered (0x80). + // Subtracting from 0 produces either 0x01, 0x00, 0xff, or 0x80. Note that the effect is to + // exchange less for greater (and vice versa), while leaving equivalent and unordered unchanged. + return partial_ordering{static_cast<_Compare_ord>(0 - static_cast(_Val._Value))}; } -#endif // __cpp_impl_three_way_comparison private: - _NODISCARD constexpr bool _Is_ordered() const noexcept { - return _Value != static_cast<_Compare_t>(_Compare_ncmp::unordered); - } - _Compare_t _Value; }; @@ -144,21 +130,7 @@ public: return _Val._Value == 0; } -#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L - _NODISCARD friend constexpr bool operator==(const weak_ordering&, const weak_ordering&) noexcept = default; -#else // ^^^ supports <=> and P1185 / supports neither vvv - _NODISCARD friend constexpr bool operator!=(const weak_ordering _Val, _Literal_zero) noexcept { - return _Val._Value != 0; - } - - _NODISCARD friend constexpr bool operator==(_Literal_zero, const weak_ordering _Val) noexcept { - return 0 == _Val._Value; - } - - _NODISCARD friend constexpr bool operator!=(_Literal_zero, const weak_ordering _Val) noexcept { - return 0 != _Val._Value; - } -#endif // defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L + _NODISCARD friend constexpr bool operator==(weak_ordering, weak_ordering) noexcept = default; _NODISCARD friend constexpr bool operator<(const weak_ordering _Val, _Literal_zero) noexcept { return _Val._Value < 0; @@ -177,22 +149,21 @@ public: } _NODISCARD friend constexpr bool operator<(_Literal_zero, const weak_ordering _Val) noexcept { - return 0 < _Val._Value; + return _Val > 0; } _NODISCARD friend constexpr bool operator>(_Literal_zero, const weak_ordering _Val) noexcept { - return 0 > _Val._Value; + return _Val < 0; } _NODISCARD friend constexpr bool operator<=(_Literal_zero, const weak_ordering _Val) noexcept { - return 0 <= _Val._Value; + return _Val >= 0; } _NODISCARD friend constexpr bool operator>=(_Literal_zero, const weak_ordering _Val) noexcept { - return 0 >= _Val._Value; + return _Val <= 0; } -#ifdef __cpp_impl_three_way_comparison _NODISCARD friend constexpr weak_ordering operator<=>(const weak_ordering _Val, _Literal_zero) noexcept { return _Val; } @@ -200,7 +171,6 @@ public: _NODISCARD friend constexpr weak_ordering operator<=>(_Literal_zero, const weak_ordering _Val) noexcept { return weak_ordering{static_cast<_Compare_ord>(-_Val._Value)}; } -#endif // __cpp_impl_three_way_comparison private: _Compare_t _Value; @@ -235,21 +205,7 @@ public: return _Val._Value == 0; } -#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L - _NODISCARD friend constexpr bool operator==(const strong_ordering&, const strong_ordering&) noexcept = default; -#else // ^^^ supports <=> and P1185 / supports neither vvv - _NODISCARD friend constexpr bool operator!=(const strong_ordering _Val, _Literal_zero) noexcept { - return _Val._Value != 0; - } - - _NODISCARD friend constexpr bool operator==(_Literal_zero, const strong_ordering _Val) noexcept { - return 0 == _Val._Value; - } - - _NODISCARD friend constexpr bool operator!=(_Literal_zero, const strong_ordering _Val) noexcept { - return 0 != _Val._Value; - } -#endif // defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L + _NODISCARD friend constexpr bool operator==(strong_ordering, strong_ordering) noexcept = default; _NODISCARD friend constexpr bool operator<(const strong_ordering _Val, _Literal_zero) noexcept { return _Val._Value < 0; @@ -268,22 +224,21 @@ public: } _NODISCARD friend constexpr bool operator<(_Literal_zero, const strong_ordering _Val) noexcept { - return 0 < _Val._Value; + return _Val > 0; } _NODISCARD friend constexpr bool operator>(_Literal_zero, const strong_ordering _Val) noexcept { - return 0 > _Val._Value; + return _Val < 0; } _NODISCARD friend constexpr bool operator<=(_Literal_zero, const strong_ordering _Val) noexcept { - return 0 <= _Val._Value; + return _Val >= 0; } _NODISCARD friend constexpr bool operator>=(_Literal_zero, const strong_ordering _Val) noexcept { - return 0 >= _Val._Value; + return _Val <= 0; } -#ifdef __cpp_impl_three_way_comparison _NODISCARD friend constexpr strong_ordering operator<=>(const strong_ordering _Val, _Literal_zero) noexcept { return _Val; } @@ -291,7 +246,6 @@ public: _NODISCARD friend constexpr strong_ordering operator<=>(_Literal_zero, const strong_ordering _Val) noexcept { return strong_ordering{static_cast<_Compare_ord>(-_Val._Value)}; } -#endif // __cpp_impl_three_way_comparison private: _Compare_t _Value; @@ -364,7 +318,7 @@ struct common_comparison_category { using type = common_comparison_category_t<_Types...>; }; -#if defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#ifdef __cpp_lib_concepts // clang-format off template concept _Compares_as = same_as, _Cat>; @@ -409,7 +363,7 @@ struct compare_three_way { using is_transparent = int; }; // clang-format on -#endif // defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#endif // __cpp_lib_concepts // Other components not yet implemented _STD_END diff --git a/stl/inc/span b/stl/inc/span index d61d8e375fc..8565955bfc5 100644 --- a/stl/inc/span +++ b/stl/inc/span @@ -12,13 +12,11 @@ #if !_HAS_CXX20 #pragma message("The contents of are available only with C++20 or later.") #else // ^^^ !_HAS_CXX20 / _HAS_CXX20 vvv +#include #include #include #include -#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L -#include -#endif // defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) @@ -153,7 +151,6 @@ struct _Span_iterator { return _Myptr == _Right._Myptr; } -#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L _NODISCARD constexpr strong_ordering operator<=>(const _Span_iterator& _Right) const noexcept { #if _ITERATOR_DEBUG_LEVEL >= 1 _STL_VERIFY( @@ -161,31 +158,6 @@ struct _Span_iterator { #endif // _ITERATOR_DEBUG_LEVEL >= 1 return _Myptr <=> _Right._Myptr; } -#else // ^^^ use spaceship / no spaceship vvv - _NODISCARD constexpr bool operator!=(const _Span_iterator& _Right) const noexcept { - return !(*this == _Right); - } - - _NODISCARD constexpr bool operator<(const _Span_iterator& _Right) const noexcept { -#if _ITERATOR_DEBUG_LEVEL >= 1 - _STL_VERIFY( - _Mybegin == _Right._Mybegin && _Myend == _Right._Myend, "cannot compare incompatible span iterators"); -#endif // _ITERATOR_DEBUG_LEVEL >= 1 - return _Myptr < _Right._Myptr; - } - - _NODISCARD constexpr bool operator>(const _Span_iterator& _Right) const noexcept { - return _Right < *this; - } - - _NODISCARD constexpr bool operator<=(const _Span_iterator& _Right) const noexcept { - return !(_Right < *this); - } - - _NODISCARD constexpr bool operator>=(const _Span_iterator& _Right) const noexcept { - return !(*this < _Right); - } -#endif // defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201902L #if _ITERATOR_DEBUG_LEVEL >= 1 friend constexpr void _Verify_range(const _Span_iterator& _First, const _Span_iterator& _Last) noexcept { diff --git a/stl/inc/xutility b/stl/inc/xutility index 0728eaacc6c..f80ed07fa86 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -2105,13 +2105,13 @@ _NODISCARD _CONSTEXPR17 bool operator>=(const reverse_iterator<_BidIt1>& _Left, #endif // __cpp_lib_concepts { return _Left._Get_current() <= _Right._Get_current(); } -#if defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#ifdef __cpp_lib_concepts template _BidIt2> _NODISCARD constexpr compare_three_way_result_t<_BidIt1, _BidIt2> operator<=>( const reverse_iterator<_BidIt1>& _Left, const reverse_iterator<_BidIt2>& _Right) { return _Right._Get_current() <=> _Left._Get_current(); } -#endif // defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#endif // __cpp_lib_concepts template _NODISCARD _CONSTEXPR17 auto operator-(const reverse_iterator<_BidIt1>& _Left, const reverse_iterator<_BidIt2>& _Right) @@ -4103,12 +4103,12 @@ _NODISCARD _CONSTEXPR17 bool operator==(const move_iterator<_Iter1>& _Left, cons #endif // __cpp_lib_concepts { return _Left.base() == _Right.base(); } -#if !_HAS_CXX20 || __cpp_impl_three_way_comparison < 201902L +#if !_HAS_CXX20 template _NODISCARD _CONSTEXPR17 bool operator!=(const move_iterator<_Iter1>& _Left, const move_iterator<_Iter2>& _Right) { return !(_Left == _Right); } -#endif // !_HAS_CXX20 || __cpp_impl_three_way_comparison < 201902L +#endif // !_HAS_CXX20 template _NODISCARD _CONSTEXPR17 bool operator<(const move_iterator<_Iter1>& _Left, const move_iterator<_Iter2>& _Right) @@ -4148,13 +4148,13 @@ _NODISCARD _CONSTEXPR17 bool operator>=(const move_iterator<_Iter1>& _Left, cons #endif // __cpp_lib_concepts { return !(_Left < _Right); } -#if defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#ifdef __cpp_lib_concepts template _Iter2> _NODISCARD constexpr compare_three_way_result_t<_Iter1, _Iter2> operator<=>( const move_iterator<_Iter1>& _Left, const move_iterator<_Iter2>& _Right) { return _Left.base() <=> _Right.base(); } -#endif // defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#endif // __cpp_lib_concepts template _NODISCARD _CONSTEXPR17 auto operator-(const move_iterator<_Iter1>& _Left, const move_iterator<_Iter2>& _Right) @@ -4195,21 +4195,6 @@ namespace _Unreachable_sentinel_detail { _NODISCARD friend constexpr bool operator==(const unreachable_sentinel_t&, const _Winc&) noexcept { return false; } -#if !defined(__cpp_impl_three_way_comparison) || __cpp_impl_three_way_comparison < 201902L - template - _NODISCARD friend constexpr bool operator==(const _Winc&, const unreachable_sentinel_t&) noexcept { - return false; - } - - template - _NODISCARD friend constexpr bool operator!=(const unreachable_sentinel_t&, const _Winc&) noexcept { - return true; - } - template - _NODISCARD friend constexpr bool operator!=(const _Winc&, const unreachable_sentinel_t&) noexcept { - return true; - } -#endif // !defined(__cpp_impl_three_way_comparison) || __cpp_impl_three_way_comparison < 201902L }; } // namespace _Unreachable_sentinel_detail struct unreachable_sentinel_t : _Unreachable_sentinel_detail::_Base {}; // TRANSITION, /permissive- @@ -5236,7 +5221,7 @@ _NODISCARD bool lexicographical_compare( } #endif // _HAS_CXX17 -#if defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#ifdef __cpp_lib_concepts // FUNCTION TEMPLATE lexicographical_compare_three_way template _NODISCARD constexpr auto lexicographical_compare_three_way( @@ -5294,7 +5279,7 @@ _NODISCARD constexpr auto lexicographical_compare_three_way( _InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2) { return _STD lexicographical_compare_three_way(_First1, _Last1, _First2, _Last2, compare_three_way{}); } -#endif // defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#endif // __cpp_lib_concepts // FUNCTION TEMPLATE find template diff --git a/tests/std/tests/P0768R1_spaceship_operator/test.cpp b/tests/std/tests/P0768R1_spaceship_operator/test.cpp index 09a23f15d6b..33a1218f7d6 100644 --- a/tests/std/tests/P0768R1_spaceship_operator/test.cpp +++ b/tests/std/tests/P0768R1_spaceship_operator/test.cpp @@ -7,64 +7,89 @@ #include #include -enum class comp { equal, nonequal, less, greater, unordered }; +enum class comp { equal, less, greater, unordered }; template -constexpr bool test_ord(T val) { +constexpr bool test_order(const T val) { + // Validate that val is ordered relative to literal zero according to Z assert((val == 0) == (Z == comp::equal)); assert((0 == val) == (Z == comp::equal)); + assert((val != 0) == (Z != comp::equal)); assert((0 != val) == (Z != comp::equal)); -#ifdef __cpp_impl_three_way_comparison - assert(((val <=> 0) == 0) == (Z == comp::equal)); - assert(((0 <=> val) == 0) == (Z == comp::equal)); - -#if __cpp_impl_three_way_comparison >= 201907L - assert(val == val); - assert(!(val != val)); -#endif // __cpp_impl_three_way_comparison >= 201907L -#endif // __cpp_impl_three_way_comparison - assert(std::is_eq(val) == (Z == comp::equal)); - assert(std::is_neq(val) == (Z != comp::equal)); assert((val < 0) == (Z == comp::less)); assert((0 > val) == (Z == comp::less)); + assert((val > 0) == (Z == comp::greater)); assert((0 < val) == (Z == comp::greater)); + assert((val <= 0) == (Z != comp::greater && Z != comp::unordered)); assert((0 >= val) == (Z != comp::greater && Z != comp::unordered)); + assert((val >= 0) == (Z != comp::less && Z != comp::unordered)); assert((0 <= val) == (Z != comp::less && Z != comp::unordered)); -#ifdef __cpp_impl_three_way_comparison - assert(((val <=> 0) < 0) == (Z == comp::less)); - assert(((0 <=> val) < 0) == (Z == comp::greater)); -#if __cpp_impl_three_way_comparison >= 201907L - assert(val == val); - assert(!(val != val)); -#endif // __cpp_impl_three_way_comparison >= 201907L -#endif // __cpp_impl_three_way_comparison + assert(std::is_eq(val) == (Z == comp::equal)); + assert(std::is_neq(val) == (Z != comp::equal)); assert(std::is_lt(val) == (Z == comp::less)); assert(std::is_lteq(val) == (Z != comp::greater && Z != comp::unordered)); assert(std::is_gt(val) == (Z == comp::greater)); assert(std::is_gteq(val) == (Z != comp::less && Z != comp::unordered)); + // Validate that equality is reflexive for comparison category types + assert(val == val); + assert(!(val != val)); + return true; } -static_assert(test_ord(std::partial_ordering::equivalent)); -static_assert(test_ord(std::partial_ordering::less)); -static_assert(test_ord(std::partial_ordering::greater)); -static_assert(test_ord(std::partial_ordering::unordered)); +constexpr bool test_orderings() { + assert(test_order(std::partial_ordering::equivalent)); + assert(test_order(std::partial_ordering::less)); + assert(test_order(std::partial_ordering::greater)); + assert(test_order(std::partial_ordering::unordered)); + + assert(test_order(std::weak_ordering::equivalent)); + assert(test_order(std::weak_ordering::less)); + assert(test_order(std::weak_ordering::greater)); + + assert(test_order(std::strong_ordering::equal)); + assert(test_order(std::strong_ordering::equivalent)); + assert(test_order(std::strong_ordering::less)); + assert(test_order(std::strong_ordering::greater)); -static_assert(test_ord(std::weak_ordering::equivalent)); -static_assert(test_ord(std::weak_ordering::less)); -static_assert(test_ord(std::weak_ordering::greater)); + return true; +} + +constexpr bool test_spaceships() { + // Exhaustively validate x <=> 0 and 0 <=> x for all values of each comparison category type. + // Guards against regression of GH-1050: "0 <=> partial_ordering::unordered returns invalid value". + assert(std::partial_ordering::less <=> 0 == std::partial_ordering::less); + assert(0 <=> std::partial_ordering::less == std::partial_ordering::greater); + assert(std::partial_ordering::equivalent <=> 0 == std::partial_ordering::equivalent); + assert(0 <=> std::partial_ordering::equivalent == std::partial_ordering::equivalent); + assert(std::partial_ordering::greater <=> 0 == std::partial_ordering::greater); + assert(0 <=> std::partial_ordering::greater == std::partial_ordering::less); + assert(std::partial_ordering::unordered <=> 0 == std::partial_ordering::unordered); + assert(0 <=> std::partial_ordering::unordered == std::partial_ordering::unordered); + + assert(std::weak_ordering::less <=> 0 == std::weak_ordering::less); + assert(0 <=> std::weak_ordering::less == std::weak_ordering::greater); + assert(std::weak_ordering::equivalent <=> 0 == std::weak_ordering::equivalent); + assert(0 <=> std::weak_ordering::equivalent == std::weak_ordering::equivalent); + assert(std::weak_ordering::greater <=> 0 == std::weak_ordering::greater); + assert(0 <=> std::weak_ordering::greater == std::weak_ordering::less); + + assert(std::strong_ordering::less <=> 0 == std::strong_ordering::less); + assert(0 <=> std::strong_ordering::less == std::strong_ordering::greater); + assert(std::strong_ordering::equal <=> 0 == std::strong_ordering::equal); + assert(0 <=> std::strong_ordering::equal == std::strong_ordering::equal); + assert(std::strong_ordering::greater <=> 0 == std::strong_ordering::greater); + assert(0 <=> std::strong_ordering::greater == std::strong_ordering::less); -static_assert(test_ord(std::strong_ordering::equal)); -static_assert(test_ord(std::strong_ordering::equivalent)); -static_assert(test_ord(std::strong_ordering::less)); -static_assert(test_ord(std::strong_ordering::greater)); + return true; +} template constexpr bool test_common_cc = std::is_same_v, Expected>; @@ -121,7 +146,7 @@ static_assert(test_common_type()); static_assert(test_common_type()); static_assert(test_common_type()); -#if defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#ifdef __cpp_lib_concepts constexpr auto my_cmp_three_way = [](const auto& left, const auto& right) { return left <=> right; }; template @@ -187,29 +212,21 @@ void test_algorithm() { assert((test_algorithm2())); assert((test_algorithm2())); } -#endif // defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#endif // __cpp_lib_concepts int main() { - test_ord(std::partial_ordering::equivalent); - test_ord(std::partial_ordering::less); - test_ord(std::partial_ordering::greater); - test_ord(std::partial_ordering::unordered); - - test_ord(std::weak_ordering::equivalent); - test_ord(std::weak_ordering::less); - test_ord(std::weak_ordering::greater); + static_assert(test_orderings()); + test_orderings(); - test_ord(std::strong_ordering::equal); - test_ord(std::strong_ordering::equivalent); - test_ord(std::strong_ordering::less); - test_ord(std::strong_ordering::greater); + static_assert(test_spaceships()); + test_spaceships(); -#if defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#ifdef __cpp_lib_concepts test_algorithm(); test_algorithm(); test_algorithm(); test_algorithm(); test_algorithm(); test_algorithm(); -#endif // defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_concepts) +#endif // __cpp_lib_concepts } From 6c044964f814013ac17daaac198cef3e6ce7a59d Mon Sep 17 00:00:00 2001 From: Billy O'Neal Date: Tue, 21 Jul 2020 00:55:06 -0700 Subject: [PATCH 07/50] Enable [[msvc::noop_dtor]] from Visual Studio 2019 version 16.8. (#1016) Co-authored-by: Casey Carter Co-authored-by: Stephan T. Lavavej --- stl/inc/system_error | 25 +++++++++++++++++++++++-- stl/src/excptptr.cpp | 28 ++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/stl/inc/system_error b/stl/inc/system_error index 4121c5766d9..bb73811a85c 100644 --- a/stl/inc/system_error +++ b/stl/inc/system_error @@ -513,8 +513,7 @@ public: } }; -// -// TRANSITION, VSO-1043450 +// TRANSITION, Visual Studio 2019 version 16.8 // _Immortalize_memcpy_image is used to provide a nonstandard guarantee. // Specifically, we want the error category objects returned from things like std::system_category() to always // be available, even during DLL unload (otherwise, would be a huge regression vs. legacy error codes). @@ -557,6 +556,28 @@ _NODISCARD const _Ty& _Immortalize_memcpy_image() noexcept { static constexpr _Ty _Static; return _Static; } +#elif defined(_MSC_VER) && _MSC_VER > 1927 && !defined(_M_CEE) // _M_CEE test is TRANSITION, VSO-1153256 +template +struct _Constexpr_immortalize_impl { + union { + _Ty _Storage; + }; + + constexpr _Constexpr_immortalize_impl() noexcept : _Storage{} {} + + _Constexpr_immortalize_impl(const _Constexpr_immortalize_impl&) = delete; + _Constexpr_immortalize_impl& operator=(const _Constexpr_immortalize_impl&) = delete; + + [[msvc::noop_dtor]] ~_Constexpr_immortalize_impl() { + // do nothing, allowing _Ty to be used during shutdown + } +}; + +template +_NODISCARD const _Ty& _Immortalize_memcpy_image() noexcept { + static _Constexpr_immortalize_impl<_Ty> _Static; + return _Static._Storage; +} #else // choose immortalize strategy template _NODISCARD const _Ty& _Immortalize_memcpy_image() noexcept { diff --git a/stl/src/excptptr.cpp b/stl/src/excptptr.cpp index 821697998c6..b0e85953014 100644 --- a/stl/src/excptptr.cpp +++ b/stl/src/excptptr.cpp @@ -37,13 +37,37 @@ extern "C" _CRTIMP2 void* __cdecl __AdjustPointer(void*, const PMD&); // defined using namespace std; namespace { -#ifdef _M_CEE_PURE +#if defined(_M_CEE_PURE) template _Ty& _Immortalize() { // return a reference to an object that will live forever /* MAGIC */ static _Immortalizer_impl<_Ty> _Static; return reinterpret_cast<_Ty&>(_Static._Storage); } -#else // ^^^ _M_CEE_PURE ^^^ // vvv !_M_CEE_PURE vvv +#elif _MSC_VER > 1927 && !defined(_M_CEE) // _M_CEE test is TRANSITION, VSO-1153256 + template + struct _Constexpr_excptptr_immortalize_impl { + union { + _Ty _Storage; + }; + + constexpr _Constexpr_excptptr_immortalize_impl() noexcept : _Storage{} {} + + _Constexpr_excptptr_immortalize_impl(const _Constexpr_excptptr_immortalize_impl&) = delete; + _Constexpr_excptptr_immortalize_impl& operator=(const _Constexpr_excptptr_immortalize_impl&) = delete; + + [[msvc::noop_dtor]] ~_Constexpr_excptptr_immortalize_impl() { + // do nothing, allowing _Ty to be used during shutdown + } + }; + + template + _Constexpr_excptptr_immortalize_impl<_Ty> _Immortalize_impl; + + template + _NODISCARD _Ty& _Immortalize() noexcept { + return _Immortalize_impl<_Ty>._Storage; + } +#else // choose immortalize strategy template int __stdcall _Immortalize_impl(void*, void* _Storage_ptr, void**) noexcept { // adapt True Placement New to _Execute_once From c10ae01b4d9508eed9d5f059a120ee7223b6ac12 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Tue, 21 Jul 2020 11:00:51 +0300 Subject: [PATCH 08/50] Atomic CAS with pad (#23, P0528R3) (#1029) Co-authored-by: Stephan T. Lavavej Co-authored-by: Billy Robert O'Neal III Co-authored-by: Casey Carter --- stl/inc/atomic | 158 +++++++++++- stl/inc/execution | 14 - tests/std/test.lst | 1 + tests/std/tests/P0528R3_cmpxchg_pad/env.lst | 4 + tests/std/tests/P0528R3_cmpxchg_pad/test.cpp | 254 +++++++++++++++++++ 5 files changed, 410 insertions(+), 21 deletions(-) create mode 100644 tests/std/tests/P0528R3_cmpxchg_pad/env.lst create mode 100644 tests/std/tests/P0528R3_cmpxchg_pad/test.cpp diff --git a/stl/inc/atomic b/stl/inc/atomic index 8881fae37a8..80377e2c722 100644 --- a/stl/inc/atomic +++ b/stl/inc/atomic @@ -115,7 +115,52 @@ _NODISCARD extern "C" bool __cdecl __std_atomic_has_cmpxchg16b() noexcept; #define ATOMIC_LLONG_LOCK_FREE 2 #define ATOMIC_POINTER_LOCK_FREE 2 +// Padding bits should not participate in cmpxchg comparison starting in C++20. +// Clang does not have __builtin_zero_non_value_bits to exclude these bits to implement this C++20 feature. +// The EDG front-end substitutes everything and runs into incomplete types passed to atomic. +#if _HAS_CXX20 && !defined(__clang__) /* TRANSITION, LLVM-46685 */ && !defined(__EDG__) +#define _CMPXCHG_MASK_OUT_PADDING_BITS 1 +#else +#define _CMPXCHG_MASK_OUT_PADDING_BITS 0 +#endif + _STD_BEGIN +// STRUCT TEMPLATE _Storage_for +#if _CMPXCHG_MASK_OUT_PADDING_BITS +struct _Form_mask_t {}; +_INLINE_VAR constexpr _Form_mask_t _Form_mask{}; +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS + +template +struct _Storage_for { + // uninitialized space to store a _Ty + alignas(_Ty) unsigned char _Storage[sizeof(_Ty)]; + + _Storage_for() = default; + _Storage_for(const _Storage_for&) = delete; + _Storage_for& operator=(const _Storage_for&) = delete; + +#if _CMPXCHG_MASK_OUT_PADDING_BITS + explicit _Storage_for(_Form_mask_t) noexcept { + _CSTD memset(_Storage, 0xff, sizeof(_Ty)); + __builtin_zero_non_value_bits(_Ptr()); + } +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS + + _NODISCARD _Ty& _Ref() noexcept { + return reinterpret_cast<_Ty&>(_Storage); + } + + _NODISCARD _Ty* _Ptr() noexcept { + return reinterpret_cast<_Ty*>(&_Storage); + } +}; + +#if _CMPXCHG_MASK_OUT_PADDING_BITS +template +inline constexpr bool _Might_have_non_value_bits = + !has_unique_object_representations_v<_Ty> && !is_floating_point_v<_Ty>; +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS // FENCES extern "C" inline void atomic_thread_fence(const memory_order _Order) noexcept { @@ -355,13 +400,27 @@ struct _Atomic_storage { const auto _Storage_ptr = _STD addressof(_Storage); const auto _Expected_ptr = _STD addressof(_Expected); bool _Result; +#if _CMPXCHG_MASK_OUT_PADDING_BITS + __builtin_zero_non_value_bits(_Expected_ptr); +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS _Lock(); - if (_CSTD memcmp(_Storage_ptr, _Expected_ptr, sizeof(_Ty)) == 0) { +#if _CMPXCHG_MASK_OUT_PADDING_BITS + if constexpr (_Might_have_non_value_bits<_Ty>) { + _Storage_for<_Ty> _Local; + const auto _Local_ptr = _Local._Ptr(); + _CSTD memcpy(_Local_ptr, _Storage_ptr, sizeof(_Ty)); + __builtin_zero_non_value_bits(_Local_ptr); + _Result = _CSTD memcmp(_Local_ptr, _Expected_ptr, sizeof(_Ty)) == 0; + } else { + _Result = _CSTD memcmp(_Storage_ptr, _Expected_ptr, sizeof(_Ty)) == 0; + } +#else // _CMPXCHG_MASK_OUT_PADDING_BITS + _Result = _CSTD memcmp(_Storage_ptr, _Expected_ptr, sizeof(_Ty)) == 0; +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS + if (_Result) { _CSTD memcpy(_Storage_ptr, _STD addressof(_Desired), sizeof(_Ty)); - _Result = true; } else { _CSTD memcpy(_Expected_ptr, _Storage_ptr, sizeof(_Ty)); - _Result = false; } _Unlock(); @@ -480,8 +539,29 @@ struct _Atomic_storage<_Ty, 1> { // lock-free using 1-byte intrinsics bool compare_exchange_strong(_Ty& _Expected, const _Ty _Desired, const memory_order _Order = memory_order_seq_cst) noexcept { // CAS with given memory order - const char _Expected_bytes = _Atomic_reinterpret_as(_Expected); // read before atomic operation + char _Expected_bytes = _Atomic_reinterpret_as(_Expected); // read before atomic operation char _Prev_bytes; + +#if _CMPXCHG_MASK_OUT_PADDING_BITS + if constexpr (_Might_have_non_value_bits<_Ty>) { + _Storage_for<_Ty> _Mask{_Form_mask}; + const char _Mask_val = _Atomic_reinterpret_as(_Mask._Ref()); + + for (;;) { + _ATOMIC_CHOOSE_INTRINSIC(_Order, _Prev_bytes, _InterlockedCompareExchange8, + _Atomic_address_as(_Storage), _Atomic_reinterpret_as(_Desired), _Expected_bytes); + if (_Prev_bytes == _Expected_bytes) { + return true; + } + + if ((_Prev_bytes ^ _Expected_bytes) & _Mask_val) { + reinterpret_cast(_Expected) = _Prev_bytes; + return false; + } + _Expected_bytes = (_Expected_bytes & _Mask_val) | (_Prev_bytes & ~_Mask_val); + } + } +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS _ATOMIC_CHOOSE_INTRINSIC(_Order, _Prev_bytes, _InterlockedCompareExchange8, _Atomic_address_as(_Storage), _Atomic_reinterpret_as(_Desired), _Expected_bytes); if (_Prev_bytes == _Expected_bytes) { @@ -562,8 +642,28 @@ struct _Atomic_storage<_Ty, 2> { // lock-free using 2-byte intrinsics bool compare_exchange_strong(_Ty& _Expected, const _Ty _Desired, const memory_order _Order = memory_order_seq_cst) noexcept { // CAS with given memory order - const short _Expected_bytes = _Atomic_reinterpret_as(_Expected); // read before atomic operation + short _Expected_bytes = _Atomic_reinterpret_as(_Expected); // read before atomic operation short _Prev_bytes; +#if _CMPXCHG_MASK_OUT_PADDING_BITS + if constexpr (_Might_have_non_value_bits<_Ty>) { + _Storage_for<_Ty> _Mask{_Form_mask}; + const short _Mask_val = _Atomic_reinterpret_as(_Mask._Ref()); + + for (;;) { + _ATOMIC_CHOOSE_INTRINSIC(_Order, _Prev_bytes, _InterlockedCompareExchange16, + _Atomic_address_as(_Storage), _Atomic_reinterpret_as(_Desired), _Expected_bytes); + if (_Prev_bytes == _Expected_bytes) { + return true; + } + + if ((_Prev_bytes ^ _Expected_bytes) & _Mask_val) { + _CSTD memcpy(_STD addressof(_Expected), &_Prev_bytes, sizeof(_Ty)); + return false; + } + _Expected_bytes = (_Expected_bytes & _Mask_val) | (_Prev_bytes & ~_Mask_val); + } + } +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS _ATOMIC_CHOOSE_INTRINSIC(_Order, _Prev_bytes, _InterlockedCompareExchange16, _Atomic_address_as(_Storage), _Atomic_reinterpret_as(_Desired), _Expected_bytes); if (_Prev_bytes == _Expected_bytes) { @@ -642,8 +742,28 @@ struct _Atomic_storage<_Ty, 4> { // lock-free using 4-byte intrinsics bool compare_exchange_strong(_Ty& _Expected, const _Ty _Desired, const memory_order _Order = memory_order_seq_cst) noexcept { // CAS with given memory order - const long _Expected_bytes = _Atomic_reinterpret_as(_Expected); // read before atomic operation + long _Expected_bytes = _Atomic_reinterpret_as(_Expected); // read before atomic operation long _Prev_bytes; +#if _CMPXCHG_MASK_OUT_PADDING_BITS + if constexpr (_Might_have_non_value_bits<_Ty>) { + _Storage_for<_Ty> _Mask{_Form_mask}; + const long _Mask_val = _Atomic_reinterpret_as(_Mask); + + for (;;) { + _ATOMIC_CHOOSE_INTRINSIC(_Order, _Prev_bytes, _InterlockedCompareExchange, + _Atomic_address_as(_Storage), _Atomic_reinterpret_as(_Desired), _Expected_bytes); + if (_Prev_bytes == _Expected_bytes) { + return true; + } + + if ((_Prev_bytes ^ _Expected_bytes) & _Mask_val) { + _CSTD memcpy(_STD addressof(_Expected), &_Prev_bytes, sizeof(_Ty)); + return false; + } + _Expected_bytes = (_Expected_bytes & _Mask_val) | (_Prev_bytes & ~_Mask_val); + } + } +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS _ATOMIC_CHOOSE_INTRINSIC(_Order, _Prev_bytes, _InterlockedCompareExchange, _Atomic_address_as(_Storage), _Atomic_reinterpret_as(_Desired), _Expected_bytes); if (_Prev_bytes == _Expected_bytes) { @@ -749,8 +869,30 @@ struct _Atomic_storage<_Ty, 8> { // lock-free using 8-byte intrinsics bool compare_exchange_strong(_Ty& _Expected, const _Ty _Desired, const memory_order _Order = memory_order_seq_cst) noexcept { // CAS with given memory order - const long long _Expected_bytes = _Atomic_reinterpret_as(_Expected); // read before atomic operation + long long _Expected_bytes = _Atomic_reinterpret_as(_Expected); // read before atomic operation long long _Prev_bytes; + +#if _CMPXCHG_MASK_OUT_PADDING_BITS + if constexpr (_Might_have_non_value_bits<_Ty>) { + _Storage_for<_Ty> _Mask{_Form_mask}; + const long long _Mask_val = _Atomic_reinterpret_as(_Mask); + + for (;;) { + _ATOMIC_CHOOSE_INTRINSIC(_Order, _Prev_bytes, _InterlockedCompareExchange64, + _Atomic_address_as(_Storage), _Atomic_reinterpret_as(_Desired), + _Expected_bytes); + if (_Prev_bytes == _Expected_bytes) { + return true; + } + + if ((_Prev_bytes ^ _Expected_bytes) & _Mask_val) { + _CSTD memcpy(_STD addressof(_Expected), &_Prev_bytes, sizeof(_Ty)); + return false; + } + _Expected_bytes = (_Expected_bytes & _Mask_val) | (_Prev_bytes & ~_Mask_val); + } + } +#endif // _CMPXCHG_MASK_OUT_PADDING_BITS _ATOMIC_CHOOSE_INTRINSIC(_Order, _Prev_bytes, _InterlockedCompareExchange64, _Atomic_address_as(_Storage), _Atomic_reinterpret_as(_Desired), _Expected_bytes); if (_Prev_bytes == _Expected_bytes) { @@ -2103,6 +2245,8 @@ inline void atomic_flag_clear_explicit(volatile atomic_flag* _Flag, memory_order _STD_END +#undef _CMPXCHG_MASK_OUT_PADDING_BITS + #undef _ATOMIC_CHOOSE_INTRINSIC #undef _ATOMIC_HAS_DCAS diff --git a/stl/inc/execution b/stl/inc/execution index d632e4c4788..18ccbc1d496 100644 --- a/stl/inc/execution +++ b/stl/inc/execution @@ -3590,20 +3590,6 @@ _FwdIt partition(_ExPo&&, _FwdIt _First, const _FwdIt _Last, _Pr _Pred) noexcept } // PARALLEL FUNCTION TEMPLATE set_intersection -template -struct _Storage_for { - // uninitialized space to store a _Ty - alignas(_Ty) unsigned char _Storage[sizeof(_Ty)]; - - _Storage_for() = default; - _Storage_for(const _Storage_for&) = delete; - _Storage_for& operator=(const _Storage_for&) = delete; - - _Ty& _Ref() { - return reinterpret_cast<_Ty&>(_Storage); - } -}; - inline constexpr unsigned char _Local_available = 1; inline constexpr unsigned char _Sum_available = 2; diff --git a/tests/std/test.lst b/tests/std/test.lst index 8b46ac33bfa..c30028652af 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -220,6 +220,7 @@ tests\P0433R2_deduction_guides tests\P0476R2_bit_cast tests\P0487R1_fixing_operator_shl_basic_istream_char_pointer tests\P0513R0_poisoning_the_hash +tests\P0528R3_cmpxchg_pad tests\P0553R4_bit_rotating_and_counting_functions tests\P0556R3_bit_integral_power_of_two_operations tests\P0586R2_integer_comparison diff --git a/tests/std/tests/P0528R3_cmpxchg_pad/env.lst b/tests/std/tests/P0528R3_cmpxchg_pad/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P0528R3_cmpxchg_pad/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/P0528R3_cmpxchg_pad/test.cpp b/tests/std/tests/P0528R3_cmpxchg_pad/test.cpp new file mode 100644 index 00000000000..44419f57410 --- /dev/null +++ b/tests/std/tests/P0528R3_cmpxchg_pad/test.cpp @@ -0,0 +1,254 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +struct X0 { + void operator&() const = delete; +}; + + +struct X1 { + char x : 6; + + void operator&() const = delete; + + void set(const char v) { + x = v; + } + + bool check(const char v) const { + return x == v; + } +}; + +struct X2 { + short x : 9; + + void operator&() const = delete; + + void set(const char v) { + x = v; + } + + bool check(const char v) const { + return x == v; + } +}; + +#pragma pack(push, 1) +struct X3 { + char x : 4; + char : 2; + char y : 1; + short z; + + void operator&() const = delete; + + void set(const char v) { + x = v; + y = 0; + z = ~v; + } + + bool check(const char v) const { + return x == v && z == ~v; + } +}; +#pragma pack(pop) + +#pragma warning(push) +#pragma warning(disable : 4324) // '%s': structure was padded due to alignment specifier +struct alignas(4) X4 { + char x; + + void operator&() const = delete; + + void set(const char v) { + x = v; + } + + bool check(const char v) const { + return x == v; + } +}; +#pragma warning(pop) + +#pragma warning(push) +#pragma warning(disable : 4324) // '%s': structure was padded due to alignment specifier +struct X6 { + char x; + alignas(2) char y[2]; + char z; + + void operator&() const = delete; + + void set(const char v) { + x = v; + std::memset(&y, 0, sizeof(y)); + z = ~v; + } + + bool check(const char v) const { + return x == v && z == ~v; + } +}; +#pragma warning(pop) + +struct X8 { + char x; + long y; + + void operator&() const = delete; + + void set(const char v) { + x = v; + y = 0; + } + + bool check(const char v) const { + return x == v; + } +}; + +#pragma pack(push, 1) +struct X9 { + X8 x; + char z; + + void operator&() const = delete; + + void set(const char v) { + x.set(v); + z = ~v; + } + + bool check(const char v) const { + return x.check(v) && z == ~v; + } +}; +#pragma pack(pop) + +struct X16 { + long x; + char y; + long long z; + + void operator&() const = delete; + + void set(const char v) { + x = v; + y = 0; + z = ~v; + } + + bool check(const char v) const { + return x == v && z == ~v; + } +}; + +struct X20 { + long x; + long y[3]; + char z; + + void operator&() const = delete; + + void set(const char v) { + x = v; + std::memset(&y, 0, sizeof(y)); + z = ~v; + } + + bool check(const char v) const { + return x == v && z == ~v; + } +}; + + +template +void test() { + static_assert(sizeof(X) == S, "Unexpected size"); + static_assert( + !std::has_unique_object_representations_v, "Type without padding is not useful for testing P0528."); + X x1; + X x2; + X x3; + X x4; + std::memset(std::addressof(x1), 0xaa, sizeof(x1)); + std::memset(std::addressof(x2), 0x55, sizeof(x2)); + std::memset(std::addressof(x3), 0x55, sizeof(x3)); + std::memset(std::addressof(x4), 0x55, sizeof(x4)); + x1.set(5); + x2.set(5); + x3.set(6); + x4.set(7); + + std::atomic v; + v.store(x1); + X x; + std::memcpy(std::addressof(x), std::addressof(x3), sizeof(x)); + assert(!v.compare_exchange_strong(x, x4)); + assert(v.load().check(5)); + + v.store(x1); + for (int retry = 0; retry != 10; ++retry) { + X xw; + std::memcpy(std::addressof(xw), std::addressof(x3), sizeof(x)); + assert(!v.compare_exchange_weak(xw, x4)); + assert(v.load().check(5)); + } + + v.store(x1); + std::memcpy(std::addressof(x), std::addressof(x2), sizeof(x)); + assert(v.compare_exchange_strong(x, x3)); + assert(v.load().check(6)); + + v.store(x1); + for (;;) { + X xw; + std::memcpy(std::addressof(xw), std::addressof(x2), sizeof(x)); + if (v.compare_exchange_weak(xw, x3)) { + break; + } + } + assert(v.load().check(6)); +} + + +template +void test0() { + X x1; + X x2; + X x3; + std::memset(std::addressof(x1), 0xaa, sizeof(x1)); + std::memset(std::addressof(x2), 0x55, sizeof(x2)); + std::memset(std::addressof(x3), 0x55, sizeof(x3)); + + std::atomic v; + v.store(x1); + X x; + std::memcpy(std::addressof(x), std::addressof(x3), sizeof(x)); + + assert(v.compare_exchange_strong(x, x2)); +} + +int main() { +#ifndef __clang__ // TRANSITION, LLVM-46685 + test0(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); +#endif // !__clang__, TRANSITION, LLVM-46685 + return 0; +} From 1555e0b861eb20d41672aab22963b053816621ae Mon Sep 17 00:00:00 2001 From: pawREP Date: Mon, 27 Jul 2020 20:06:41 +0200 Subject: [PATCH 09/50] Vectorize reverse_copy() (#804) Co-authored-by: Billy Robert O'Neal III Co-authored-by: Stephan T. Lavavej --- stl/inc/algorithm | 52 ++++++ stl/inc/xutility | 116 +++--------- stl/src/vector_algorithms.cpp | 176 ++++++++++++++++-- .../VSO_0000000_vector_algorithms/test.cpp | 74 ++++++++ 4 files changed, 309 insertions(+), 109 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 9f5ea0f97cf..94bae9604e6 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -17,6 +17,26 @@ _STL_DISABLE_CLANG_WARNINGS #pragma push_macro("new") #undef new +#if _USE_STD_VECTOR_ALGORITHMS +_EXTERN_C +// The "noalias" attribute tells the compiler optimizer that pointers going into these hand-vectorized algorithms +// won't be stored beyond the lifetime of the function, and that the function will only reference arrays denoted by +// those pointers. The optimizer also assumes in that case that a pointer parameter is not returned to the caller via +// the return value, so functions using "noalias" must usually return void. This attribute is valuable because these +// functions are in native code objects that the compiler cannot analyze. In the absence of the noalias attribute, the +// compiler has to assume that the denoted arrays are "globally address taken", and that any later calls to +// unanalyzable routines may modify those arrays. +__declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_1( + const void* _First, const void* _Last, void* _Dest) noexcept; +__declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_2( + const void* _First, const void* _Last, void* _Dest) noexcept; +__declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_4( + const void* _First, const void* _Last, void* _Dest) noexcept; +__declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_8( + const void* _First, const void* _Last, void* _Dest) noexcept; +_END_EXTERN_C +#endif // _USE_STD_VECTOR_ALGORITHMS + _STD_BEGIN // COMMON SORT PARAMETERS _INLINE_VAR constexpr int _ISORT_MAX = 32; // maximum size for insertion sort @@ -4520,6 +4540,7 @@ namespace ranges { constexpr bool _Allow_vectorization = conjunction_v<_Is_trivially_swappable<_Elem>, negation>>; +#pragma warning(suppress : 6326) // Potential comparison of a constant with another constant if constexpr (_Allow_vectorization && _Nx <= 8 && (_Nx & (_Nx - 1)) == 0) { if (!_STD is_constant_evaluated()) { _Elem* const _First_addr = _STD to_address(_First); @@ -4584,6 +4605,37 @@ _CONSTEXPR20 _OutIt reverse_copy(_BidIt _First, _BidIt _Last, _OutIt _Dest) { const auto _UFirst = _Get_unwrapped(_First); auto _ULast = _Get_unwrapped(_Last); auto _UDest = _Get_unwrapped_n(_Dest, _Idl_distance<_BidIt>(_UFirst, _ULast)); + +#if _HAS_IF_CONSTEXPR && _USE_STD_VECTOR_ALGORITHMS + using _Elem = remove_pointer_t; + using _DestElem = remove_pointer_t; + constexpr bool _Allow_vectorization = conjunction_v, remove_const_t<_DestElem>>, + is_pointer, is_trivially_copyable<_Elem>, negation>>; + constexpr size_t _Nx = sizeof(_Elem); + +#pragma warning(suppress : 6326) // Potential comparison of a constant with another constant + if constexpr (_Allow_vectorization && _Nx <= 8 && (_Nx & (_Nx - 1)) == 0) { +#ifdef __cpp_lib_is_constant_evaluated + if (!_STD is_constant_evaluated()) +#endif // __cpp_lib_is_constant_evaluated + { + if constexpr (_Nx == 1) { + __std_reverse_copy_trivially_copyable_1(_UFirst, _ULast, _UDest); + } else if constexpr (_Nx == 2) { + __std_reverse_copy_trivially_copyable_2(_UFirst, _ULast, _UDest); + } else if constexpr (_Nx == 4) { + __std_reverse_copy_trivially_copyable_4(_UFirst, _ULast, _UDest); + } else { + __std_reverse_copy_trivially_copyable_8(_UFirst, _ULast, _UDest); + } + + _UDest += _ULast - _UFirst; + _Seek_wrapped(_Dest, _UDest); + return _Dest; + } + } +#endif // _HAS_IF_CONSTEXPR && _USE_STD_VECTOR_ALGORITHMS + for (; _UFirst != _ULast; ++_UDest) { *_UDest = *--_ULast; } diff --git a/stl/inc/xutility b/stl/inc/xutility index f80ed07fa86..ff8a7aa2d63 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -5481,116 +5481,42 @@ _NODISCARD _CONSTEXPR20 bool _Check_match_counts( } // FUNCTION TEMPLATE reverse -#if _HAS_IF_CONSTEXPR template _CONSTEXPR20 void reverse(const _BidIt _First, const _BidIt _Last) { // reverse elements in [_First, _Last) _Adl_verify_range(_First, _Last); auto _UFirst = _Get_unwrapped(_First); auto _ULast = _Get_unwrapped(_Last); -#if _USE_STD_VECTOR_ALGORITHMS +#if _HAS_IF_CONSTEXPR && _USE_STD_VECTOR_ALGORITHMS using _Elem = remove_pointer_t; constexpr bool _Allow_vectorization = conjunction_v, _Is_trivially_swappable<_Elem>, negation>>; + constexpr size_t _Nx = sizeof(_Elem); - if constexpr (_Allow_vectorization && sizeof(_Elem) == 1) { -#ifdef __cpp_lib_is_constant_evaluated - if (!_STD is_constant_evaluated()) -#endif // __cpp_lib_is_constant_evaluated - { - __std_reverse_trivially_swappable_1(_UFirst, _ULast); - return; - } - } else if constexpr (_Allow_vectorization && sizeof(_Elem) == 2) { -#ifdef __cpp_lib_is_constant_evaluated - if (!_STD is_constant_evaluated()) -#endif // __cpp_lib_is_constant_evaluated - { - __std_reverse_trivially_swappable_2(_UFirst, _ULast); - return; - } - } else if constexpr (_Allow_vectorization && sizeof(_Elem) == 4) { +#pragma warning(suppress : 6326) // Potential comparison of a constant with another constant + if constexpr (_Allow_vectorization && _Nx <= 8 && (_Nx & (_Nx - 1)) == 0) { #ifdef __cpp_lib_is_constant_evaluated if (!_STD is_constant_evaluated()) #endif // __cpp_lib_is_constant_evaluated { - __std_reverse_trivially_swappable_4(_UFirst, _ULast); - return; - } - } else if constexpr (_Allow_vectorization && sizeof(_Elem) == 8) { -#ifdef __cpp_lib_is_constant_evaluated - if (!_STD is_constant_evaluated()) -#endif // __cpp_lib_is_constant_evaluated - { - __std_reverse_trivially_swappable_8(_UFirst, _ULast); + if constexpr (_Nx == 1) { + __std_reverse_trivially_swappable_1(_UFirst, _ULast); + } else if constexpr (_Nx == 2) { + __std_reverse_trivially_swappable_2(_UFirst, _ULast); + } else if constexpr (_Nx == 4) { + __std_reverse_trivially_swappable_4(_UFirst, _ULast); + } else { + __std_reverse_trivially_swappable_8(_UFirst, _ULast); + } + return; } } -#endif // _USE_STD_VECTOR_ALGORITHMS +#endif // _HAS_IF_CONSTEXPR && _USE_STD_VECTOR_ALGORITHMS for (; _UFirst != _ULast && _UFirst != --_ULast; ++_UFirst) { _STD iter_swap(_UFirst, _ULast); } } -#else // ^^^ _HAS_IF_CONSTEXPR / !_HAS_IF_CONSTEXPR vvv -template -void _Reverse_unchecked1(_BidIt _First, _BidIt _Last, integral_constant) { - // reverse elements in [_First, _Last), general bidirectional iterators - for (; _First != _Last && _First != --_Last; ++_First) { - _STD iter_swap(_First, _Last); - } -} - -#if _USE_STD_VECTOR_ALGORITHMS -template -void _Reverse_unchecked1(const _BidIt _First, const _BidIt _Last, integral_constant) { - // reverse elements in [_First, _Last), pointers to trivially swappable of size 1 - __std_reverse_trivially_swappable_1(_First, _Last); -} - -template -void _Reverse_unchecked1(const _BidIt _First, const _BidIt _Last, integral_constant) { - // reverse elements in [_First, _Last), pointers to trivially swappable of size 2 - __std_reverse_trivially_swappable_2(_First, _Last); -} - -template -void _Reverse_unchecked1(const _BidIt _First, const _BidIt _Last, integral_constant) { - // reverse elements in [_First, _Last), pointers to trivially swappable of size 4 - __std_reverse_trivially_swappable_4(_First, _Last); -} - -template -void _Reverse_unchecked1(const _BidIt _First, const _BidIt _Last, integral_constant) { - // reverse elements in [_First, _Last), pointers to trivially swappable of size 8 - __std_reverse_trivially_swappable_8(_First, _Last); -} -#endif // _USE_STD_VECTOR_ALGORITHMS - -template -void _Reverse_unchecked(const _BidIt _First, const _BidIt _Last) { - // reverse elements in [_First, _Last), choose optimization -#if _USE_STD_VECTOR_ALGORITHMS - using _Elem = remove_pointer_t<_BidIt>; - constexpr size_t _Opt = - is_pointer_v<_BidIt> // - && _Is_trivially_swappable_v<_Elem> // - && !is_volatile_v<_Elem> // - && (sizeof(_Elem) == 1 || sizeof(_Elem) == 2 || sizeof(_Elem) == 4 || sizeof(_Elem) == 8) - ? sizeof(_Elem) - : 0; -#else // ^^^ vectorize / no vectorize vvv - constexpr size_t _Opt = 0; -#endif // _USE_STD_VECTOR_ALGORITHMS - _Reverse_unchecked1(_First, _Last, integral_constant{}); -} - -template -void reverse(const _BidIt _First, const _BidIt _Last) { - // reverse elements in [_First, _Last) - _Adl_verify_range(_First, _Last); - _Reverse_unchecked(_Get_unwrapped(_First), _Get_unwrapped(_Last)); -} -#endif // _HAS_IF_CONSTEXPR #if _HAS_CXX17 template = 0> @@ -5694,19 +5620,19 @@ _FwdIt _Rotate_unchecked1(_FwdIt _First, _FwdIt _Mid, _FwdIt _Last, forward_iter template _BidIt _Rotate_unchecked1(_BidIt _First, _BidIt _Mid, _BidIt _Last, bidirectional_iterator_tag) { // rotate [_First, _Last) left by distance(_First, _Mid) positions, bidirectional iterators - _Reverse_unchecked(_First, _Mid); - _Reverse_unchecked(_Mid, _Last); + _STD reverse(_First, _Mid); + _STD reverse(_Mid, _Last); auto _Tmp = _Reverse_until_sentinel_unchecked(_First, _Mid, _Last); - _Reverse_unchecked(_Tmp.first, _Tmp.second); + _STD reverse(_Tmp.first, _Tmp.second); return _Mid != _Tmp.first ? _Tmp.first : _Tmp.second; } template _RanIt _Rotate_unchecked1(_RanIt _First, _RanIt _Mid, _RanIt _Last, random_access_iterator_tag) { // rotate [_First, _Last) left by distance(_First, _Mid) positions, random-access iterators - _Reverse_unchecked(_First, _Mid); - _Reverse_unchecked(_Mid, _Last); - _Reverse_unchecked(_First, _Last); + _STD reverse(_First, _Mid); + _STD reverse(_Mid, _Last); + _STD reverse(_First, _Last); return _First + (_Last - _Mid); } diff --git a/stl/src/vector_algorithms.cpp b/stl/src/vector_algorithms.cpp index 2467b2b0626..4823e4fdd9d 100644 --- a/stl/src/vector_algorithms.cpp +++ b/stl/src/vector_algorithms.cpp @@ -24,20 +24,31 @@ static void _Reverse_tail(_BidIt _First, _BidIt _Last) noexcept { } } -static size_t _Byte_length(void* _First, void* _Last) noexcept { - return static_cast(_Last) - static_cast(_First); +template +static void _Reverse_copy_tail(_BidIt _First, _BidIt _Last, _OutIt _Dest) noexcept { + while (_First != _Last) { + *_Dest++ = *--_Last; + } +} + +static size_t _Byte_length(const void* _First, const void* _Last) noexcept { + return static_cast(_Last) - static_cast(_First); } static void _Advance_bytes(void*& _Target, ptrdiff_t _Offset) noexcept { _Target = static_cast(_Target) + _Offset; } +static void _Advance_bytes(const void*& _Target, ptrdiff_t _Offset) noexcept { + _Target = static_cast(_Target) + _Offset; +} + extern "C" { __declspec(noalias) void __cdecl __std_swap_ranges_trivially_swappable_noalias( void* _First1, void* _Last1, void* _First2) noexcept { constexpr size_t _Mask_32 = ~((static_cast(1) << 5) - 1); if (_Byte_length(_First1, _Last1) >= 32 && _bittest(&__isa_enabled, __ISA_AVAILABLE_AVX2)) { - void* _Stop_at = _First1; + const void* _Stop_at = _First1; _Advance_bytes(_Stop_at, _Byte_length(_First1, _Last1) & _Mask_32); do { const __m256i _Left = _mm256_loadu_si256(static_cast<__m256i*>(_First1)); @@ -55,7 +66,7 @@ __declspec(noalias) void __cdecl __std_swap_ranges_trivially_swappable_noalias( && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE2) #endif // _M_IX86 ) { - void* _Stop_at = _First1; + const void* _Stop_at = _First1; _Advance_bytes(_Stop_at, _Byte_length(_First1, _Last1) & _Mask_16); do { const __m128i _Left = _mm_loadu_si128(static_cast<__m128i*>(_First1)); @@ -70,7 +81,7 @@ __declspec(noalias) void __cdecl __std_swap_ranges_trivially_swappable_noalias( #if defined(_M_X64) // NOTE: UNALIGNED MEMORY ACCESSES constexpr size_t _Mask_8 = ~((static_cast(1) << 3) - 1); if (_Byte_length(_First1, _Last1) >= 8) { - void* _Stop_at = _First1; + const void* _Stop_at = _First1; _Advance_bytes(_Stop_at, _Byte_length(_First1, _Last1) & _Mask_8); do { const unsigned long long _Left = *static_cast(_First1); @@ -84,7 +95,7 @@ __declspec(noalias) void __cdecl __std_swap_ranges_trivially_swappable_noalias( #elif defined(_M_IX86) // NOTE: UNALIGNED MEMORY ACCESSES constexpr size_t _Mask_4 = ~((static_cast(1) << 2) - 1); if (_Byte_length(_First1, _Last1) >= 4) { - void* _Stop_at = _First1; + const void* _Stop_at = _First1; _Advance_bytes(_Stop_at, _Byte_length(_First1, _Last1) & _Mask_4); do { const unsigned long _Left = *static_cast(_First1); @@ -120,7 +131,7 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_1(void* _Firs const __m256i _Reverse_char_lanes_avx = _mm256_set_epi8( // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); - void* _Stop_at = _First; + const void* _Stop_at = _First; _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 6 << 5); do { _Advance_bytes(_Last, -32); @@ -138,7 +149,7 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_1(void* _Firs if (_Byte_length(_First, _Last) >= 32 && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE42)) { const __m128i _Reverse_char_sse = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); - void* _Stop_at = _First; + const void* _Stop_at = _First; _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 5 << 4); do { _Advance_bytes(_Last, -16); @@ -160,7 +171,7 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_2(void* _Firs const __m256i _Reverse_short_lanes_avx = _mm256_set_epi8( // 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, // 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14); - void* _Stop_at = _First; + const void* _Stop_at = _First; _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 6 << 5); do { _Advance_bytes(_Last, -32); @@ -176,7 +187,7 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_2(void* _Firs if (_Byte_length(_First, _Last) >= 32 && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE42)) { const __m128i _Reverse_short_sse = _mm_set_epi8(1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14); - void* _Stop_at = _First; + const void* _Stop_at = _First; _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 5 << 4); do { _Advance_bytes(_Last, -16); @@ -195,7 +206,7 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_2(void* _Firs __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_4(void* _First, void* _Last) noexcept { if (_Byte_length(_First, _Last) >= 64 && _bittest(&__isa_enabled, __ISA_AVAILABLE_AVX2)) { - void* _Stop_at = _First; + const void* _Stop_at = _First; _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 6 << 5); do { _Advance_bytes(_Last, -32); @@ -214,7 +225,7 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_4(void* _Firs && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE2) #endif // _M_IX86 ) { - void* _Stop_at = _First; + const void* _Stop_at = _First; _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 5 << 4); do { _Advance_bytes(_Last, -16); @@ -233,7 +244,7 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_4(void* _Firs __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_8(void* _First, void* _Last) noexcept { if (_Byte_length(_First, _Last) >= 64 && _bittest(&__isa_enabled, __ISA_AVAILABLE_AVX2)) { - void* _Stop_at = _First; + const void* _Stop_at = _First; _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 6 << 5); do { _Advance_bytes(_Last, -32); @@ -252,7 +263,7 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_8(void* _Firs && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE2) #endif // _M_IX86 ) { - void* _Stop_at = _First; + const void* _Stop_at = _First; _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 5 << 4); do { _Advance_bytes(_Last, -16); @@ -269,6 +280,143 @@ __declspec(noalias) void __cdecl __std_reverse_trivially_swappable_8(void* _Firs _Reverse_tail(static_cast(_First), static_cast(_Last)); } +__declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_1( + const void* _First, const void* _Last, void* _Dest) noexcept { + if (_Byte_length(_First, _Last) >= 32 && _bittest(&__isa_enabled, __ISA_AVAILABLE_AVX2)) { + const __m256i _Reverse_char_lanes_avx = _mm256_set_epi8( // + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + const void* _Stop_at = _Dest; + _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 5 << 5); + do { + _Advance_bytes(_Last, -32); + const __m256i _Block = _mm256_permute4x64_epi64(_mm256_loadu_si256(static_cast(_Last)), 78); + const __m256i _Block_reversed = _mm256_shuffle_epi8(_Block, _Reverse_char_lanes_avx); + _mm256_storeu_si256(static_cast<__m256i*>(_Dest), _Block_reversed); + _Advance_bytes(_Dest, 32); + } while (_Dest != _Stop_at); + } + + if (_Byte_length(_First, _Last) >= 16 && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE42)) { + const __m128i _Reverse_char_sse = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + const void* _Stop_at = _Dest; + _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 4 << 4); + do { + _Advance_bytes(_Last, -16); + const __m128i _Block = _mm_loadu_si128(static_cast(_Last)); + const __m128i _Block_reversed = _mm_shuffle_epi8(_Block, _Reverse_char_sse); // SSSE3 + _mm_storeu_si128(static_cast<__m128i*>(_Dest), _Block_reversed); + _Advance_bytes(_Dest, 16); + } while (_Dest != _Stop_at); + } + + _Reverse_copy_tail(static_cast(_First), static_cast(_Last), + static_cast(_Dest)); +} + +__declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_2( + const void* _First, const void* _Last, void* _Dest) noexcept { + if (_Byte_length(_First, _Last) >= 32 && _bittest(&__isa_enabled, __ISA_AVAILABLE_AVX2)) { + const __m256i _Reverse_short_lanes_avx = _mm256_set_epi8( // + 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, // + 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14); + const void* _Stop_at = _Dest; + _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 5 << 5); + do { + _Advance_bytes(_Last, -32); + const __m256i _Block = _mm256_permute4x64_epi64(_mm256_loadu_si256(static_cast(_Last)), 78); + const __m256i _Block_reversed = _mm256_shuffle_epi8(_Block, _Reverse_short_lanes_avx); + _mm256_storeu_si256(static_cast<__m256i*>(_Dest), _Block_reversed); + _Advance_bytes(_Dest, 32); + } while (_Dest != _Stop_at); + } + + if (_Byte_length(_First, _Last) >= 16 && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE42)) { + const __m128i _Reverse_short_sse = _mm_set_epi8(1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14); + const void* _Stop_at = _Dest; + _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 4 << 4); + do { + _Advance_bytes(_Last, -16); + const __m128i _Block = _mm_loadu_si128(static_cast(_Last)); + const __m128i _Block_reversed = _mm_shuffle_epi8(_Block, _Reverse_short_sse); // SSSE3 + _mm_storeu_si128(static_cast<__m128i*>(_Dest), _Block_reversed); + _Advance_bytes(_Dest, 16); + } while (_Dest != _Stop_at); + } + + _Reverse_copy_tail(static_cast(_First), static_cast(_Last), + static_cast(_Dest)); +} + +__declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_4( + const void* _First, const void* _Last, void* _Dest) noexcept { + if (_Byte_length(_First, _Last) >= 32 && _bittest(&__isa_enabled, __ISA_AVAILABLE_AVX2)) { + const void* _Stop_at = _Dest; + _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 5 << 5); + do { + _Advance_bytes(_Last, -32); + const __m256i _Block = _mm256_permute4x64_epi64(_mm256_loadu_si256(static_cast(_Last)), 78); + const __m256i _Block_reversed = _mm256_shuffle_epi32(_Block, 27); + _mm256_storeu_si256(static_cast<__m256i*>(_Dest), _Block_reversed); + _Advance_bytes(_Dest, 32); + } while (_Dest != _Stop_at); + } + + if (_Byte_length(_First, _Last) >= 16 +#ifdef _M_IX86 + && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE2) +#endif // _M_IX86 + ) { + const void* _Stop_at = _Dest; + _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 4 << 4); + do { + _Advance_bytes(_Last, -16); + const __m128i _Block = _mm_loadu_si128(static_cast(_Last)); + const __m128i _Block_reversed = _mm_shuffle_epi32(_Block, 27); + _mm_storeu_si128(static_cast<__m128i*>(_Dest), _Block_reversed); + _Advance_bytes(_Dest, 16); + } while (_Dest != _Stop_at); + } + + _Reverse_copy_tail(static_cast(_First), static_cast(_Last), + static_cast(_Dest)); +} + +__declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_8( + const void* _First, const void* _Last, void* _Dest) noexcept { + if (_Byte_length(_First, _Last) >= 32 && _bittest(&__isa_enabled, __ISA_AVAILABLE_AVX2)) { + const void* _Stop_at = _Dest; + _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 5 << 5); + do { + _Advance_bytes(_Last, -32); + const __m256i _Block = _mm256_loadu_si256(static_cast(_Last)); + const __m256i _Block_reversed = _mm256_permute4x64_epi64(_Block, 27); + _mm256_storeu_si256(static_cast<__m256i*>(_Dest), _Block_reversed); + _Advance_bytes(_Dest, 32); + } while (_Dest != _Stop_at); + } + + if (_Byte_length(_First, _Last) >= 16 +#ifdef _M_IX86 + && _bittest(&__isa_enabled, __ISA_AVAILABLE_SSE2) +#endif // _M_IX86 + ) { + const void* _Stop_at = _Dest; + _Advance_bytes(_Stop_at, _Byte_length(_First, _Last) >> 4 << 4); + do { + _Advance_bytes(_Last, -16); + const __m128i _Block = _mm_loadu_si128(static_cast(_Last)); + const __m128i _Block_reversed = _mm_shuffle_epi32(_Block, 78); + _mm_storeu_si128(static_cast<__m128i*>(_Dest), _Block_reversed); + _Advance_bytes(_Dest, 16); + } while (_Dest != _Stop_at); + } + + _Reverse_copy_tail(static_cast(_First), static_cast(_Last), + static_cast(_Dest)); +} + + } // extern "C" #endif // (defined(_M_IX86) || defined(_M_X64)) && !defined(_M_CEE_PURE) diff --git a/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp b/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp index 6483c50a943..b37274ecb19 100644 --- a/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp +++ b/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include #include @@ -47,6 +49,26 @@ void test_reverse(mt19937_64& gen) { } } +template +void test_case_reverse_copy(vector& input) { + auto expected = input; + last_known_good_reverse(expected.begin(), expected.end()); + vector output(input.size(), T{}); + assert(reverse_copy(input.begin(), input.end(), output.begin()) == output.end()); + assert(expected == output); +} + +template +void test_reverse_copy(mt19937_64& gen) { + vector input; + input.reserve(dataCount); + test_case_reverse_copy(input); + for (size_t attempts = 0; attempts < dataCount; ++attempts) { + input.push_back(static_cast(gen())); // intentionally narrows + test_case_reverse_copy(input); + } +} + template inline FwdIt2 last_known_good_swap_ranges(FwdIt1 first1, const FwdIt1 last1, FwdIt2 dest) { for (; first1 != last1; ++first1, ++dest) { @@ -98,6 +120,19 @@ void test_vector_algorithms() { test_reverse(gen); test_reverse(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_reverse_copy(gen); + test_swap_ranges(gen); test_swap_ranges(gen); test_swap_ranges(gen); @@ -105,8 +140,47 @@ void test_vector_algorithms() { test_swap_ranges(gen); } +template +void test_two_containers() { + Container1 one = {10, 20, 30, 40, 50}; + Container2 two = {-1, -1, -1, -1, -1}; + static constexpr int reversed[] = {50, 40, 30, 20, 10}; + + assert(reverse_copy(one.begin(), one.end(), two.begin()) == two.end()); + assert(equal(two.begin(), two.end(), begin(reversed), end(reversed))); + + static constexpr int squares[] = {1, 4, 9, 16, 25}; + static constexpr int cubes[] = {1, 8, 27, 64, 125}; + one.assign(begin(squares), end(squares)); + two.assign(begin(cubes), end(cubes)); + + assert(swap_ranges(one.begin(), one.end(), two.begin()) == two.end()); + assert(equal(one.begin(), one.end(), begin(cubes), end(cubes))); + assert(equal(two.begin(), two.end(), begin(squares), end(squares))); +} + +template +void test_one_container() { + Container x = {10, 20, 30, 40, 50}; + static constexpr int reversed[] = {50, 40, 30, 20, 10}; + + reverse(x.begin(), x.end()); + assert(equal(x.begin(), x.end(), begin(reversed), end(reversed))); + + test_two_containers>(); + test_two_containers>(); + test_two_containers>(); +} + +void test_various_containers() { + test_one_container>(); // contiguous, vectorizable + test_one_container>(); // random-access, not vectorizable + test_one_container>(); // bidi, not vectorizable +} + int main() { test_vector_algorithms(); + test_various_containers(); #ifndef _M_CEE_PURE #if defined(_M_IX86) || defined(_M_X64) disable_instructions(__ISA_AVAILABLE_AVX2); From 8e8770cb1635386e84c8ab12f0df47fb0e35951c Mon Sep 17 00:00:00 2001 From: statementreply Date: Tue, 28 Jul 2020 02:17:11 +0800 Subject: [PATCH 10/50] : Fix discrete_distribution result out of range (#1025) Mathematically, `_Par0._Pcdf.back()` should be one. However, when it is actually slightly smaller than one due to rounding error, there is a small probability that `_Px > _Par0._Pcdf.back()` and the original code returns the invalid value of `_Par0._Pcdf.size()`. --- stl/inc/random | 2 +- tests/std/test.lst | 1 + .../bad_random_engine.hpp | 191 ++++++++++++++++++ .../env.lst | 4 + .../test.cpp | 17 ++ 5 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 tests/std/tests/GH_001017_discrete_distribution_out_of_range/bad_random_engine.hpp create mode 100644 tests/std/tests/GH_001017_discrete_distribution_out_of_range/env.lst create mode 100644 tests/std/tests/GH_001017_discrete_distribution_out_of_range/test.cpp diff --git a/stl/inc/random b/stl/inc/random index 4427cd637a5..47ceeab3e55 100644 --- a/stl/inc/random +++ b/stl/inc/random @@ -4507,7 +4507,7 @@ private: result_type _Eval(_Engine& _Eng, const param_type& _Par0) const { double _Px = _NRAND(_Eng, double); const auto _First = _Par0._Pcdf.begin(); - const auto _Position = _STD lower_bound(_First, _Par0._Pcdf.end(), _Px); + const auto _Position = _STD lower_bound(_First, _Prev_iter(_Par0._Pcdf.end()), _Px); return static_cast(_Position - _First); } diff --git a/tests/std/test.lst b/tests/std/test.lst index c30028652af..85df9a7d165 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -161,6 +161,7 @@ tests\GH_000685_condition_variable_any tests\GH_000690_overaligned_function tests\GH_000890_pow_template tests\GH_001010_filesystem_error_encoding +tests\GH_001017_discrete_distribution_out_of_range tests\LWG2597_complex_branch_cut tests\LWG3018_shared_ptr_function tests\P0024R2_parallel_algorithms_adjacent_difference diff --git a/tests/std/tests/GH_001017_discrete_distribution_out_of_range/bad_random_engine.hpp b/tests/std/tests/GH_001017_discrete_distribution_out_of_range/bad_random_engine.hpp new file mode 100644 index 00000000000..d57d20bb641 --- /dev/null +++ b/tests/std/tests/GH_001017_discrete_distribution_out_of_range/bad_random_engine.hpp @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include +#include +#include +#include +#include + +namespace detail { + struct bad_rng_pattern_sentinel {}; + + template ::digits> + class bad_rng_pattern_generator { // generates bit patterns for bad_random_engine + public: + using difference_type = std::ptrdiff_t; + using value_type = UInt; + using pointer = const UInt*; + using reference = const UInt&; + using iterator_category = std::input_iterator_tag; + using my_iter = bad_rng_pattern_generator; + using my_sentinel = bad_rng_pattern_sentinel; + + static constexpr value_type top_bit = value_type{1} << (Width - 1); + static constexpr value_type lower_bits = top_bit - 1; + static constexpr value_type mask_bits = top_bit | lower_bits; + static constexpr int final_bit_count = (Width - 1) / 2 + 1; + + constexpr reference operator*() const noexcept { // gets the current pattern + return current_value_; + } + + constexpr pointer operator->() const noexcept { + return ¤t_value_; + } + + constexpr my_iter& operator++() noexcept { // generates the next pattern + current_value_ = (current_value_ & lower_bits) << 1 | (current_value_ & top_bit) >> (Width - 1); + + if (current_shift_ < Width - 1 && current_bit_count_ != 0 && current_bit_count_ != Width) { + ++current_shift_; + return *this; + } + + current_shift_ = 0; + + if (current_bit_count_ < final_bit_count) { // n 1's -> n 0's + current_bit_count_ = Width - current_bit_count_; + current_value_ ^= mask_bits; + } else if (current_bit_count_ > final_bit_count) { // n 0's -> (n+1) 1's + current_bit_count_ = Width - current_bit_count_ + 1; + current_value_ = (current_value_ ^ mask_bits) << 1 | value_type{1}; + } else { // all bit patterns have been generated, back to all 0's + current_bit_count_ = 0; + current_value_ = value_type{0}; + } + + return *this; + } + + constexpr my_iter operator++(int) noexcept { + const my_iter old = *this; + ++*this; + return old; + } + + friend constexpr bool operator==(const my_iter& a, const my_iter& b) noexcept { + return *a == *b; + } + + friend constexpr bool operator!=(const my_iter& a, const my_iter& b) noexcept { + return !(a == b); + } + + friend constexpr bool operator==(const my_iter& iter, my_sentinel) noexcept { + return *iter == 0; + } + + friend constexpr bool operator!=(const my_iter& iter, const my_sentinel sentinel) noexcept { + return !(iter == sentinel); + } + + friend constexpr bool operator==(const my_sentinel sentinel, const my_iter& iter) noexcept { + return iter == sentinel; + } + + friend constexpr bool operator!=(const my_sentinel sentinel, const my_iter& iter) noexcept { + return !(iter == sentinel); + } + + private: + value_type current_value_ = 0; + int current_bit_count_ = 0; + int current_shift_ = 0; + }; +} // namespace detail + +template ::digits, int Dimension = 1> +class bad_random_engine { + // Generates bit patterns with at most two transitions between 0's and 1's. + // (e.g. 00000000, 11111111, 00001111, 11110000, 00011000, 11100111) + // When its output is grouped into subsequences of length Dimension, it cycles through all possible subsequences + // containing only such bit patterns. Bit patterns with few 1's or few 0's are generated first, starting from all + // 0's and all 1's. + + static_assert(std::is_integral_v, "bad_random_engine: UInt should be unsigned integral type"); + static_assert(std::is_unsigned_v, "bad_random_engine: UInt should be unsigned integral type"); + static_assert(Width > 0, "bad_random_engine: invalid value for Width"); + static_assert(Width <= std::numeric_limits::digits, "bad_random_engine: invalid value for Width"); + static_assert(Dimension > 0, "bad_random_engine: invalid value for Dimension"); + +public: + using result_type = UInt; + + static constexpr result_type(min)() noexcept { + return result_type{0}; + } + static constexpr result_type(max)() noexcept { + return generator::mask_bits; + } + + constexpr result_type operator()() noexcept { + const result_type result = *generators_[current_dimension_]; + + if (current_dimension_ < Dimension - 1) { + ++current_dimension_; + } else { + current_dimension_ = 0; + + if (!generate_next()) { + has_cycled_through_ = true; + } + } + + return result; + } + + constexpr bool has_cycled_through() const noexcept { // have we finished a full cycle? + return has_cycled_through_; + } + +private: + using generator = detail::bad_rng_pattern_generator; + using sentinel = detail::bad_rng_pattern_sentinel; + + constexpr bool generate_next() noexcept { // generates the next subsequence, returns false if back to all 0's + if (limit_value_ != sentinel{}) { + for (int i = 0; i < limit_dimension_; ++i) { + if (generators_[i] != limit_value_) { + ++generators_[i]; + return true; + } else { + generators_[i] = generator{}; + } + } + + for (int i = limit_dimension_ + 1; i < Dimension; ++i) { + ++generators_[i]; + if (generators_[i] != limit_value_) { + return true; + } else { + generators_[i] = generator{}; + } + } + + __analysis_assume(limit_dimension_ < Dimension); + generators_[limit_dimension_] = generator{}; + + if (limit_dimension_ < Dimension - 1) { + ++limit_dimension_; + generators_[limit_dimension_] = limit_value_; + return true; + } + } + + limit_dimension_ = 0; + generators_[0] = ++limit_value_; + return limit_value_ != sentinel{}; + } + + generator generators_[Dimension] = {}; + generator limit_value_{}; + int limit_dimension_ = 0; + int current_dimension_ = 0; + bool has_cycled_through_ = false; +}; + +// the cycle length of bad_random_generator is 32 546 312 +using bad_random_generator = bad_random_engine; diff --git a/tests/std/tests/GH_001017_discrete_distribution_out_of_range/env.lst b/tests/std/tests/GH_001017_discrete_distribution_out_of_range/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_001017_discrete_distribution_out_of_range/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_001017_discrete_distribution_out_of_range/test.cpp b/tests/std/tests/GH_001017_discrete_distribution_out_of_range/test.cpp new file mode 100644 index 00000000000..5a43af9b4df --- /dev/null +++ b/tests/std/tests/GH_001017_discrete_distribution_out_of_range/test.cpp @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include "bad_random_engine.hpp" + +int main() { + std::discrete_distribution dist{1, 1, 1, 1, 1, 1}; + bad_random_generator rng; + + while (!rng.has_cycled_through()) { + const auto rand_value = dist(rng); + assert(0 <= rand_value && rand_value < 6); + } +} From 28efc7058405b3db0ae99663f36ff1d02feedf57 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Mon, 27 Jul 2020 20:22:29 +0200 Subject: [PATCH 11/50] Implement ranges::unique family (#1039) Co-authored-by: Stephan T. Lavavej Co-authored-by: Casey Carter --- stl/inc/algorithm | 170 ++++++++++++++++++ tests/std/include/range_algorithm_support.hpp | 10 +- tests/std/test.lst | 2 + .../tests/P0896R4_ranges_alg_unique/env.lst | 4 + .../tests/P0896R4_ranges_alg_unique/test.cpp | 72 ++++++++ .../P0896R4_ranges_alg_unique_copy/env.lst | 4 + .../P0896R4_ranges_alg_unique_copy/test.cpp | 123 +++++++++++++ 7 files changed, 380 insertions(+), 5 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_unique/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_unique/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_unique_copy/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_unique_copy/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 94bae9604e6..e93442761ca 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -4354,6 +4354,72 @@ _NODISCARD _FwdIt unique(_ExPo&&, _FwdIt _First, _FwdIt _Last) noexcept /* termi } #endif // _HAS_CXX17 +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::unique + class _Unique_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Pj = identity, + indirect_equivalence_relation> _Pr = ranges::equal_to> + _NODISCARD constexpr subrange<_It> operator()(_It _First, _Se _Last, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Unique_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + return _Rewrap_subrange>(_First, _STD move(_UResult)); + } + + // clang-format off + template , _Pj>> _Pr = ranges::equal_to> + requires permutable> + _NODISCARD constexpr borrowed_subrange_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _UResult = _Unique_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + return _Rewrap_subrange>(_Range, _STD move(_UResult)); + } + // clang-format on + private: + template + _NODISCARD static constexpr subrange<_It> _Unique_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + // Remove adjacent elements from [_First, _Last) whose projections satisfy _Pred + _STL_INTERNAL_STATIC_ASSERT(permutable<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_equivalence_relation<_Pr, projected<_It, _Pj>>); + + auto _Current = _First; + if (_First == _Last) { + return {_STD move(_Current), _STD move(_First)}; + } + + for (;; ++_Current) { + if (++_First == _Last) { + return {_STD move(_Current), _STD move(_First)}; + } + + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Current), _STD invoke(_Proj, *_First))) { + break; + } + } + + while (++_First != _Last) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_Current), _STD invoke(_Proj, *_First))) { + ++_Current; + *_Current = _RANGES iter_move(_First); + } + } + ++_Current; + + return {_STD move(_Current), _STD move(_First)}; + } + }; + + inline constexpr _Unique_fn unique{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE unique_copy #if _HAS_IF_CONSTEXPR // clang-format off @@ -4527,6 +4593,110 @@ _FwdIt2 unique_copy(_ExPo&&, _FwdIt1 _First, _FwdIt1 _Last, _FwdIt2 _Dest) noexc #ifdef __cpp_lib_concepts namespace ranges { + // ALIAS TEMPLATE unique_copy_result + template + using unique_copy_result = in_out_result<_In, _Out>; + + // VARIABLE ranges::unique_copy + // clang-format off + template + concept _Can_reread_or_store = forward_iterator<_It> + || (input_iterator<_Out> && same_as, iter_value_t<_Out>>) + || indirectly_copyable_storable<_It, _Out>; + // clang-format on + class _Unique_copy_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out, class _Pj = identity, + indirect_equivalence_relation> _Pr = ranges::equal_to> + requires indirectly_copyable<_It, _Out> && _Can_reread_or_store<_It, _Out> + constexpr unique_copy_result<_It, _Out> operator()( + _It _First, _Se _Last, _Out _Result, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Unique_copy_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), + _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First), _STD move(_Result)}; + } + + template , _Pj>> _Pr = ranges::equal_to> + requires indirectly_copyable, _Out> && _Can_reread_or_store, _Out> + constexpr unique_copy_result, _Out> operator()( + _Rng&& _Range, _Out _Result, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + auto _UResult = _Unique_copy_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), + _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First), _STD move(_Result)}; + } + // clang-format on + private: + template + _NODISCARD static constexpr unique_copy_result<_It, _Out> _Unique_copy_unchecked( + _It _First, const _Se _Last, _Out _Result, _Pr _Pred, _Pj _Proj) { + // Copy elements from [_First, _Last) to _Result, compressing adjacent elements whose projections satisfy + // _Pred + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(indirect_equivalence_relation<_Pr, projected<_It, _Pj>>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_copyable<_It, _Out>); + _STL_INTERNAL_STATIC_ASSERT(_Can_reread_or_store<_It, _Out>); + + if (_First == _Last) { + return {_STD move(_First), _STD move(_Result)}; + } + + if constexpr (input_iterator<_Out> && same_as, iter_value_t<_Out>>) { + // Can reread _Result + *_Result = *_First; + + while (++_First != _Last) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_Result), _STD invoke(_Proj, *_First))) { + ++_Result; + *_Result = *_First; + } + } + } else if constexpr (forward_iterator<_It>) { + // Can reread _First + auto _Current = _First; + *_Result = *_First; + + while (++_First != _Last) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_Current), _STD invoke(_Proj, *_First))) { + _Current = _First; + ++_Result; + *_Result = *_First; + } + } + } else { + // Neither _First nor _Result can be reread, construct temporary + iter_value_t<_It> _Val = *_First; + + while (++_First != _Last) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, _Val), _STD invoke(_Proj, *_First))) { + *_Result = _STD move(_Val); + ++_Result; + _Val = *_First; + } + } + *_Result = _STD move(_Val); + } + ++_Result; + + return {_STD move(_First), _STD move(_Result)}; + } + }; + + inline constexpr _Unique_copy_fn unique_copy{_Not_quite_object::_Construct_tag{}}; + // VARIABLE ranges::reverse // clang-format off // concept-constrained for strict enforcement as it is used by several algorithms diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index 1a6b09496eb..0e766db6fda 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -1028,11 +1028,6 @@ constexpr void test_contiguous() { with_contiguous_ranges::call(); } -template -constexpr void input_range_output_iterator_permutations() { - with_input_ranges, Element1>::call(); -} - template constexpr void test_in_in() { with_input_ranges, Element1>::call(); @@ -1053,6 +1048,11 @@ constexpr void test_bidi_bidi() { with_bidirectional_ranges, Element1>::call(); } +template +constexpr void input_range_output_iterator_permutations() { + with_input_ranges, Element1>::call(); +} + template constexpr void test_in_write() { with_input_ranges, Element1>::call(); diff --git a/tests/std/test.lst b/tests/std/test.lst index 85df9a7d165..05ab704c42a 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -284,6 +284,8 @@ tests\P0896R4_ranges_alg_search_n tests\P0896R4_ranges_alg_swap_ranges tests\P0896R4_ranges_alg_transform_binary tests\P0896R4_ranges_alg_transform_unary +tests\P0896R4_ranges_alg_unique +tests\P0896R4_ranges_alg_unique_copy tests\P0896R4_ranges_iterator_machinery tests\P0896R4_ranges_range_machinery tests\P0896R4_ranges_subrange diff --git a/tests/std/tests/P0896R4_ranges_alg_unique/env.lst b/tests/std/tests/P0896R4_ranges_alg_unique/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_unique/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_unique/test.cpp b/tests/std/tests/P0896R4_ranges_alg_unique/test.cpp new file mode 100644 index 00000000000..3da1a4923e9 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_unique/test.cpp @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include +using namespace std; +using P = pair; + +// Validate dangling story +STATIC_ASSERT(same_as{})), ranges::dangling>); +STATIC_ASSERT(same_as{})), ranges::subrange>); + +struct instantiator { + static constexpr P expected[4] = {{0, 99}, {1, 47}, {3, 99}, {4, 47}}; + + static constexpr auto make_counter(size_t& counter) { + return [&counter](const int a, const int b) { + ++counter; + return a == b; + }; + } + + template + static constexpr void call() { +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 + if constexpr (!ranges::contiguous_range) +#endif // TRANSITION, VSO-938163 + { + using ranges::unique, ranges::subrange, ranges::equal, ranges::size, ranges::iterator_t; + + size_t comparisonCounter = 0; + const auto countedEq = make_counter(comparisonCounter); + + { // Validate iterator + sentinel overload + P input[6] = {{0, 99}, {1, 47}, {2, 47}, {3, 99}, {4, 47}, {5, 47}}; + ReadWrite wrapped_input{input}; + + auto result = unique(wrapped_input.begin(), wrapped_input.end(), countedEq, get_second); + STATIC_ASSERT(same_as>>); + assert(result.begin() == next(wrapped_input.begin(), 4)); + assert(result.end() == wrapped_input.end()); + assert(equal(expected, span{input}.first<4>())); + assert(comparisonCounter == size(input) - 1); + } + + comparisonCounter = 0; + + { // Validate range overload + P input[6] = {{0, 99}, {1, 47}, {2, 47}, {3, 99}, {4, 47}, {5, 47}}; + ReadWrite wrapped_input{input}; + + auto result = unique(wrapped_input, countedEq, get_second); + STATIC_ASSERT(same_as>>); + assert(result.begin() == next(wrapped_input.begin(), 4)); + assert(result.end() == wrapped_input.end()); + assert(equal(expected, span{input}.first<4>())); + assert(comparisonCounter == size(input) - 1); + } + } + } +}; + +int main() { + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_unique_copy/env.lst b/tests/std/tests/P0896R4_ranges_alg_unique_copy/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_unique_copy/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_unique_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_unique_copy/test.cpp new file mode 100644 index 00000000000..71e6a268aee --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_unique_copy/test.cpp @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include +using namespace std; +using P = pair; + +// Validate that unique_copy_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, nullptr_to)), + ranges::unique_copy_result>); +STATIC_ASSERT( + same_as{}, nullptr_to)), ranges::unique_copy_result>); + +constexpr P expectedInputRead[6] = {{0, 99}, {1, 47}, {1, 47}, {1, 99}, {1, 47}, {1, 47}}; +constexpr P expectedOutputRead[4] = {{1, 99}, {3, 47}, {2, 99}, {2, 47}}; +constexpr P expectedInput[6] = {{1, 99}, {3, 47}, {1, 47}, {2, 99}, {2, 47}, {1, 47}}; +constexpr P expectedOutput[4] = {{0, 99}, {1, 47}, {1, 99}, {1, 47}}; +constexpr auto countedProjection = [](P& value) { + ++value.first; + return value.second; +}; + +struct test_iterator_overload { + template + static constexpr void call() { + using ranges::unique_copy, ranges::unique_copy_result, ranges::equal, ranges::equal_to, ranges::iterator_t, + ranges::size; + // Validate iterator + sentinel overload + P input[6] = {{0, 99}, {0, 47}, {0, 47}, {0, 99}, {0, 47}, {0, 47}}; + P output[4] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + + const same_as, Write>> auto result = + unique_copy(wrapped_input.begin(), wrapped_input.end(), Write{output}, equal_to{}, countedProjection); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == end(output)); + if constexpr (input_iterator || !ranges::forward_range) { + assert(equal(output, expectedOutputRead)); + assert(equal(input, expectedInputRead)); + } else { + assert(equal(output, expectedOutput)); + assert(equal(input, expectedInput)); + } + } +}; + +struct test_range_overload { + template + static constexpr void call() { + using ranges::unique_copy, ranges::unique_copy_result, ranges::equal, ranges::equal_to, ranges::iterator_t, + ranges::size; + // Validate range overload + P input[6] = {{0, 99}, {0, 47}, {0, 47}, {0, 99}, {0, 47}, {0, 47}}; + P output[4] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; + Read wrapped_input{input}; + + const same_as, Write>> auto result = + unique_copy(wrapped_input, Write{output}, equal_to{}, countedProjection); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == end(output)); + if constexpr (input_iterator || !ranges::forward_range) { + assert(equal(output, expectedOutputRead)); + assert(equal(input, expectedInputRead)); + } else { + assert(equal(output, expectedOutput)); + assert(equal(input, expectedInput)); + } + } +}; + +#ifdef TEST_EVERYTHING +int main() { +#ifndef _PREFAST_ // TRANSITION, GH-1030 + STATIC_ASSERT((test_in_write(), true)); + STATIC_ASSERT((test_in_write(), true)); +#endif // TRANSITION, GH-1030 + test_in_write(); + test_in_write(); +} +#else // ^^^ test all range combinations / test only interesting range combos vvv +constexpr bool run_tests() { + // We need to test the three different implementations, so we need input_range/forward_range as input and + // output_iterator/input_iterator as output. + using namespace test; + using test::iterator, test::range; + + using in_test_range = + range; + using fwd_test_range = + range; + + using out_test_iterator = iterator; + using in_test_iterator = iterator; + + // Reread output implementation + test_iterator_overload::call(); + test_range_overload::call(); + + // Reread input implementation + test_iterator_overload::call(); + test_range_overload::call(); + + // Store implementation + test_iterator_overload::call(); + test_range_overload::call(); + + return true; +} + +int main() { + STATIC_ASSERT(run_tests()); + run_tests(); +} +#endif // TEST_EVERYTHING From 99241dce6dc36713931c568d09667e5598614b1c Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Mon, 27 Jul 2020 20:25:42 +0200 Subject: [PATCH 12/50] Implement ranges::sample and ranges::shuffle (#1052) Co-authored-by: statementreply --- stl/inc/algorithm | 214 ++++++++++++++++-- stl/inc/random | 23 -- tests/std/test.lst | 2 + .../tests/P0896R4_ranges_alg_sample/env.lst | 4 + .../tests/P0896R4_ranges_alg_sample/test.cpp | 84 +++++++ .../tests/P0896R4_ranges_alg_shuffle/env.lst | 4 + .../tests/P0896R4_ranges_alg_shuffle/test.cpp | 58 +++++ 7 files changed, 350 insertions(+), 39 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_sample/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_sample/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_shuffle/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_shuffle/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index e93442761ca..d92a975c56b 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -4856,12 +4856,12 @@ _SampleIt _Sample_reservoir_unchecked( // pre: _SampleIt is random-access && 0 < _Count && the range [_Dest, _Dest + _Count) is valid using _Diff_sample = _Iter_diff_t<_SampleIt>; const auto _SCount = static_cast<_Diff_sample>(_Count); - _Iter_diff_t<_PopIt> _PopSize{}; - for (; _PopSize < _SCount; ++_PopSize, (void) ++_First) { - // _PopSize is less than _SCount, and [_Dest, _Dest + _SCount) is valid, - // so [_Dest, _Dest + _PopSize) must be valid, so narrowing to _Diff_sample + _Iter_diff_t<_PopIt> _Pop_size{}; + for (; _Pop_size < _SCount; ++_Pop_size, (void) ++_First) { + // _Pop_size is less than _SCount, and [_Dest, _Dest + _SCount) is valid, + // so [_Dest, _Dest + _Pop_size) must be valid, so narrowing to _Diff_sample // can't overflow - const auto _Sample_pop = static_cast<_Diff_sample>(_PopSize); + const auto _Sample_pop = static_cast<_Diff_sample>(_Pop_size); if (_First == _Last) { return _Dest + _Sample_pop; } @@ -4869,7 +4869,7 @@ _SampleIt _Sample_reservoir_unchecked( *(_Dest + _Sample_pop) = *_First; } for (; _First != _Last; ++_First) { - const auto _Idx = _RngFunc(++_PopSize); + const auto _Idx = _RngFunc(++_Pop_size); if (_Idx < _SCount) { *(_Dest + static_cast<_Diff_sample>(_Idx)) = *_First; // again, valid narrowing because _Idx < _SCount } @@ -4879,12 +4879,12 @@ _SampleIt _Sample_reservoir_unchecked( template _SampleIt _Sample_selection_unchecked( - _PopIt _First, const _PopIt _Last, _Iter_diff_t<_PopIt> _PopSize, _SampleIt _Dest, _Diff _Count, _RngFn& _RngFunc) { + _PopIt _First, _Iter_diff_t<_PopIt> _Pop_size, _SampleIt _Dest, _Diff _Count, _RngFn& _RngFunc) { // source is forward *and* we know the source range size: use selection sampling (stable) - // pre: _PopIt is forward && _Count <= _PopSize + // pre: _PopIt is forward && _Count <= _Pop_size using _CT = common_type_t<_Iter_diff_t<_PopIt>, _Diff>; - for (; _Count > 0 && _First != _Last; ++_First, (void) --_PopSize) { - if (static_cast<_CT>(_RngFunc(_PopSize)) < static_cast<_CT>(_Count)) { + for (; _Pop_size > 0; ++_First, (void) --_Pop_size) { + if (static_cast<_CT>(_RngFunc(_Pop_size)) < static_cast<_CT>(_Count)) { --_Count; *_Dest = *_First; ++_Dest; @@ -4906,15 +4906,15 @@ template _SampleIt _Sample1(_PopIt _First, _PopIt _Last, _SampleIt _Dest, _Diff _Count, _RngFn& _RngFunc, forward_iterator_tag) { // source is forward: use selection sampling (stable) // pre: _Count > 0 - using _PopDiff = _Iter_diff_t<_PopIt>; - using _CT = common_type_t<_Diff, _PopDiff>; - const auto _PopSize = _STD distance(_First, _Last); - if (static_cast<_CT>(_Count) > static_cast<_CT>(_PopSize)) { - _Count = static_cast<_Diff>(_PopSize); // narrowing OK because _Count is getting smaller + using _PopDiff = _Iter_diff_t<_PopIt>; + using _CT = common_type_t<_Diff, _PopDiff>; + const auto _Pop_size = _STD distance(_First, _Last); + if (static_cast<_CT>(_Count) > static_cast<_CT>(_Pop_size)) { + _Count = static_cast<_Diff>(_Pop_size); // narrowing OK because _Count is getting smaller } _Seek_wrapped( - _Dest, _Sample_selection_unchecked(_First, _Last, _PopSize, _Get_unwrapped_n(_Dest, _Count), _Count, _RngFunc)); + _Dest, _Sample_selection_unchecked(_First, _Pop_size, _Get_unwrapped_n(_Dest, _Count), _Count, _RngFunc)); return _Dest; } @@ -4932,6 +4932,128 @@ _SampleIt sample(_PopIt _First, _PopIt _Last, _SampleIt _Dest, _Diff _Count, return _Dest; } + +#ifdef __cpp_lib_concepts +// STRUCT TEMPLATE _Require_constant +template +struct _Require_constant; // not defined; _Require_constant is a valid type if E is a constant expression + +// CONCEPT uniform_random_bit_generator +// clang-format off +template +concept uniform_random_bit_generator = invocable<_Ty&> && unsigned_integral> && requires { + { (_Ty::min)() } -> same_as>; + { (_Ty::max)() } -> same_as>; + typename _Require_constant<(_Ty::min)()>; + typename _Require_constant<(_Ty::max)()>; + requires (_Ty::min)() < (_Ty::max)(); +}; +// clang-format on + +namespace ranges { + // VARIABLE ranges::sample + class _Sample_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out, class _Urng> + requires (forward_iterator<_It> || random_access_iterator<_Out>) + && indirectly_copyable<_It, _Out> && uniform_random_bit_generator> + _Out operator()(_It _First, _Se _Last, _Out _Result, iter_difference_t<_It> _Count, _Urng&& _Func) const { + _Adl_verify_range(_First, _Last); + if (_Count <= 0) { + return _Result; + } + + _Rng_from_urng, remove_reference_t<_Urng>> _RngFunc(_Func); + if constexpr (forward_iterator<_It>) { + auto _UFirst = _Get_unwrapped(_STD move(_First)); + auto _Pop_size = _RANGES distance(_UFirst, _Get_unwrapped(_STD move(_Last))); + return _Sample_selection_unchecked(_STD move(_UFirst), _Pop_size, _STD move(_Result), _Count, _RngFunc); + } else { + return _Sample_reservoir_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), + _STD move(_Result), _Count, _RngFunc); + } + } + + template + requires (forward_range<_Rng> || random_access_iterator<_Out>) + && indirectly_copyable, _Out> + && uniform_random_bit_generator> + _Out operator()(_Rng&& _Range, _Out _Result, range_difference_t<_Rng> _Count, _Urng&& _Func) const { + if (_Count <= 0) { + return _Result; + } + + _Rng_from_urng, remove_reference_t<_Urng>> _RngFunc(_Func); + if constexpr (forward_range<_Rng>) { + auto _UFirst = _Ubegin(_Range); + auto _Pop_size = _RANGES distance(_UFirst, _Uend(_Range)); + return _Sample_selection_unchecked(_STD move(_UFirst), _Pop_size, _STD move(_Result), _Count, _RngFunc); + } else { + return _Sample_reservoir_unchecked( + _Ubegin(_Range), _Uend(_Range), _STD move(_Result), _Count, _RngFunc); + } + } + // clang-format on + private: + template + _NODISCARD static _Out _Sample_selection_unchecked( + _It _First, iter_difference_t<_It> _Pop_size, _Out _Result, iter_difference_t<_It> _Count, _Rng& _RngFunc) { + // randomly select _Count elements from [_First, _First + _Pop_size) into _Result + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_copyable<_It, _Out>); + + if (_Count > _Pop_size) { + _Count = _Pop_size; + } + + for (; _Pop_size > 0; ++_First, (void) --_Pop_size) { + if (_RngFunc(_Pop_size) < _Count) { + *_Result = *_First; + ++_Result; + if (--_Count == 0) { + break; + } + } + } + + return _Result; + } + + template + _NODISCARD static _Out _Sample_reservoir_unchecked( + _It _First, const _Se _Last, _Out _Result, const iter_difference_t<_It> _Count, _Rng& _RngFunc) { + // randomly select _Count elements from [_First, _Last) into _Result + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(random_access_iterator<_Out>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_copyable<_It, _Out>); + + iter_difference_t<_It> _Pop_size{}; + for (; _Pop_size < _Count; ++_Pop_size, (void) ++_First) { + if (_First == _Last) { + return _Result + _Pop_size; + } + + *(_Result + _Pop_size) = *_First; + } + for (; _First != _Last; ++_First) { + const auto _Idx = _RngFunc(++_Pop_size); + if (_Idx < _Count) { + *(_Result + _Idx) = *_First; + } + } + + return _Result + _Count; + } + }; + + inline constexpr _Sample_fn sample{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE shuffle WITH URNG @@ -4964,6 +5086,66 @@ void shuffle(_RanIt _First, _RanIt _Last, _Urng&& _Func) { // shuffle [_First, _ _Random_shuffle1(_First, _Last, _RngFunc); } +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::shuffle + class _Shuffle_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, class _Urng> + requires permutable<_It> && uniform_random_bit_generator> + _It operator()(_It _First, _Se _Last, _Urng&& _Func) const { + _Adl_verify_range(_First, _Last); + + _Rng_from_urng, remove_reference_t<_Urng>> _RngFunc(_Func); + auto _UResult = + _Shuffle_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _RngFunc); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; + } + + template + requires permutable> && uniform_random_bit_generator> + borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Urng&& _Func) const { + _Rng_from_urng, remove_reference_t<_Urng>> _RngFunc(_Func); + + return _Rewrap_iterator(_Range, _Shuffle_unchecked(_Ubegin(_Range), _Uend(_Range), _RngFunc)); + } + // clang-format on + private: + template + _NODISCARD static _It _Shuffle_unchecked(_It _First, const _Se _Last, _Rng& _Func) { + // shuffle [_First, _Last) using random function _Func + _STL_INTERNAL_STATIC_ASSERT(random_access_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(permutable<_It>); + + if (_First == _Last) { + return _First; + } + using _Diff = iter_difference_t<_It>; + + auto _Target = _First; + _Diff _Target_index = 1; + for (; ++_Target != _Last; ++_Target_index) { + // randomly place an element from [_First, _Target] at _Target + const _Diff _Off = _Func(_Target_index + 1); + _STL_ASSERT(0 <= _Off && _Off <= _Target_index, "random value out of range"); + if (_Off != _Target_index) { // avoid self-move-assignment + _RANGES iter_swap(_Target, _First + _Off); + } + } + return _Target; + } + }; + + inline constexpr _Shuffle_fn shuffle{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + #if _HAS_AUTO_PTR_ETC // FUNCTION TEMPLATE random_shuffle WITH RANDOM FN template diff --git a/stl/inc/random b/stl/inc/random index 47ceeab3e55..f3748cb2fb6 100644 --- a/stl/inc/random +++ b/stl/inc/random @@ -15,10 +15,6 @@ #include #include -#ifdef __cpp_lib_concepts -#include -#endif // __cpp_lib_concepts - #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) #pragma warning(disable : _STL_DISABLED_WARNINGS) @@ -52,25 +48,6 @@ _STD_BEGIN "unsigned short, unsigned int, unsigned long, or unsigned long long"); \ _RNG_PROHIBIT_CHAR(_CheckedType) - -#ifdef __cpp_lib_concepts -// STRUCT TEMPLATE _Require_constant -template -struct _Require_constant; // not defined; _Require_constant is a valid type if E is a constant expression - -// CONCEPT uniform_random_bit_generator -// clang-format off -template -concept uniform_random_bit_generator = invocable<_Ty&> && unsigned_integral> && requires { - { (_Ty::min)() } -> same_as>; - { (_Ty::max)() } -> same_as>; - typename _Require_constant<(_Ty::min)()>; - typename _Require_constant<(_Ty::max)()>; - requires (_Ty::min)() < (_Ty::max)(); -}; -// clang-format on -#endif // __cpp_lib_concepts - // ALIAS TEMPLATE _Enable_if_seed_seq_t template using _Enable_if_seed_seq_t = enable_if_t< diff --git a/tests/std/test.lst b/tests/std/test.lst index 05ab704c42a..12dbf4d64a4 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -279,8 +279,10 @@ tests\P0896R4_ranges_alg_replace_copy tests\P0896R4_ranges_alg_replace_copy_if tests\P0896R4_ranges_alg_replace_if tests\P0896R4_ranges_alg_reverse +tests\P0896R4_ranges_alg_sample tests\P0896R4_ranges_alg_search tests\P0896R4_ranges_alg_search_n +tests\P0896R4_ranges_alg_shuffle tests\P0896R4_ranges_alg_swap_ranges tests\P0896R4_ranges_alg_transform_binary tests\P0896R4_ranges_alg_transform_unary diff --git a/tests/std/tests/P0896R4_ranges_alg_sample/env.lst b/tests/std/tests/P0896R4_ranges_alg_sample/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_sample/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_sample/test.cpp b/tests/std/tests/P0896R4_ranges_alg_sample/test.cpp new file mode 100644 index 00000000000..8f0f582e5f2 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_sample/test.cpp @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include +using namespace std; + +const unsigned int seed = random_device{}(); +mt19937 gen{seed}; + +struct instantiator { + static constexpr int reservoir[5] = {13, 42, 71, 112, 1729}; + + template > Write> + static void call() { + using ranges::sample, ranges::equal, ranges::is_sorted, ranges::iterator_t; + + if constexpr (forward_iterator> || random_access_iterator) { + auto copy_gen = gen; + + { // Validate iterator + sentinel overload + int output1[3] = {-1, -1, -1}; + int output2[3] = {-1, -1, -1}; + Read wrapped_input{reservoir}; + + auto result1 = sample(wrapped_input.begin(), wrapped_input.end(), Write{output1}, 3, gen); + STATIC_ASSERT(same_as); + assert(result1.peek() == end(output1)); + + // check repeatability + Read wrapped_input2{reservoir}; + auto result2 = sample(wrapped_input2.begin(), wrapped_input2.end(), Write{output2}, 3, copy_gen); + assert(equal(output1, output2)); + assert(result2.peek() == end(output2)); + + if (ranges::forward_range) { + // verify stability + assert(is_sorted(output1)); + } else { + // ensure ordering for equality test + sort(begin(output1), end(output1)); + } + assert(includes(cbegin(reservoir), cend(reservoir), cbegin(output1), cend(output1))); + } + { // Validate range overload + int output1[3] = {-1, -1, -1}; + int output2[3] = {-1, -1, -1}; + Read wrapped_input{reservoir}; + + auto result1 = sample(wrapped_input, Write{output1}, 3, gen); + STATIC_ASSERT(same_as); + assert(result1.peek() == end(output1)); + + // check repeatability + Read wrapped_input2{reservoir}; + auto result2 = sample(wrapped_input2, Write{output2}, 3, copy_gen); + assert(equal(output1, output2)); + assert(result2.peek() == end(output2)); + + if (ranges::forward_range) { + // verify stability + assert(is_sorted(output1)); + } else { + // ensure ordering for equality test + sort(begin(output1), end(output1)); + } + assert(includes(cbegin(reservoir), cend(reservoir), cbegin(output1), cend(output1))); + } + } + } +}; + +int main() { + printf("Using seed: %u\n", seed); + + test_in_write(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_shuffle/env.lst b/tests/std/tests/P0896R4_ranges_alg_shuffle/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_shuffle/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_shuffle/test.cpp b/tests/std/tests/P0896R4_ranges_alg_shuffle/test.cpp new file mode 100644 index 00000000000..1a82decb40c --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_shuffle/test.cpp @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include +using namespace std; + +const unsigned int seed = random_device{}(); +mt19937 gen{seed}; + +// Validate dangling story +STATIC_ASSERT(same_as{}, gen)), ranges::dangling>); +STATIC_ASSERT(same_as{}, gen)), int*>); + +struct instantiator { + static constexpr int expected[7] = {1, 2, 3, 4, 5, 6, 7}; + + template + static void call() { + using ranges::shuffle, ranges::equal, ranges::is_sorted, ranges::iterator_t; + + { // Validate iterator + sentinel overload + int input[7] = {1, 2, 3, 4, 5, 6, 7}; + ReadWrite wrapped_input{input}; + + auto result = shuffle(wrapped_input.begin(), wrapped_input.end(), gen); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + + sort(begin(input), end(input)); + assert(equal(input, expected)); + } + { // Validate range overload + int input[7] = {1, 2, 3, 4, 5, 6, 7}; + ReadWrite wrapped_input{input}; + + auto result = shuffle(wrapped_input, gen); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + + sort(begin(input), end(input)); + assert(equal(input, expected)); + } + } +}; + +int main() { + printf("Using seed: %u\n", seed); + + test_random(); +} From 98f37c302a9c50d40242ebbdb4ee46595badc572 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Mon, 27 Jul 2020 20:28:04 +0200 Subject: [PATCH 13/50] Modernize the ranges::find family (#1058) Co-authored-by: Casey Carter Co-authored-by: Stephan T. Lavavej --- stl/inc/algorithm | 133 +++++++++++------ .../tests/P0896R4_ranges_alg_find/test.cpp | 93 +++++------- .../P0896R4_ranges_alg_find_first_of/test.cpp | 134 +++++++++--------- .../tests/P0896R4_ranges_alg_find_if/test.cpp | 92 ++++++------ .../P0896R4_ranges_alg_find_if_not/test.cpp | 94 ++++++------ 5 files changed, 277 insertions(+), 269 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index d92a975c56b..61b41ec854f 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -540,22 +540,40 @@ namespace ranges { indirect_unary_predicate> _Pr> _NODISCARD constexpr _It operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_STD move(_First)); - const auto _ULast = _Get_unwrapped(_STD move(_Last)); - for (; _UFirst != _ULast; ++_UFirst) { - if (!_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst))) { - break; - } - } - _Seek_wrapped(_First, _STD move(_UFirst)); + auto _UResult = _Find_if_not_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); return _First; } template , _Pj>> _Pr> _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { - return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _First = _RANGES begin(_Range); + + auto _UResult = _Find_if_not_unchecked( + _Get_unwrapped(_STD move(_First)), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; + } + + private: + template + _NODISCARD static constexpr _It _Find_if_not_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + + for (; _First != _Last; ++_First) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + break; + } + } + + return _First; } }; @@ -609,34 +627,46 @@ namespace ranges { template _Se, class _Pj = identity, indirect_binary_predicate, projected<_It, _Pj>> _Pr = ranges::equal_to> _NODISCARD constexpr _It operator()(_It _First, const _Se _Last, _Pr _Pred = {}, _Pj _Proj = {}) const { - // find first satisfying _Pred with successor _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_First); - const auto _ULast = _Get_unwrapped(_Last); - if (_UFirst == _ULast) { + auto _UResult = _Adjacent_find_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult)); + return _First; + } + + template , _Pj>, projected, _Pj>> _Pr = + ranges::equal_to> + _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _UResult = _Adjacent_find_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + + return _Rewrap_iterator(_Range, _STD move(_UResult)); + } + + private: + template + _NODISCARD static constexpr _It _Adjacent_find_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + // find first satisfying _Pred with successor + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_binary_predicate<_Pr, projected<_It, _Pj>, projected<_It, _Pj>>); + + if (_First == _Last) { return _First; } - for (auto _UNext = _UFirst;; _UFirst = _UNext) { - if (++_UNext == _ULast) { - _Seek_wrapped(_First, _UNext); - return _First; + for (auto _Next = _First;; ++_First) { + if (++_Next == _Last) { + return _Next; } - if (_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst), _STD invoke(_Proj, *_UNext))) { - _Seek_wrapped(_First, _UFirst); + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First), _STD invoke(_Proj, *_Next))) { return _First; } } } - - template , _Pj>, projected, _Pj>> _Pr = - ranges::equal_to> - _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { - return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); - } }; inline constexpr _Adjacent_find_fn adjacent_find{_Not_quite_object::_Construct_tag{}}; @@ -3204,24 +3234,16 @@ namespace ranges { template _Se1, forward_iterator _It2, sentinel_for<_It2> _Se2, class _Pr = ranges::equal_to, class _Pj1 = identity, class _Pj2 = identity> requires indirectly_comparable<_It1, _It2, _Pr, _Pj1, _Pj2> - _NODISCARD constexpr _It1 operator()(_It1 _First1, const _Se1 _Last1, const _It2 _First2, const _Se2 _Last2, - _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _NODISCARD constexpr _It1 operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, _Pr _Pred = {}, + _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { _Adl_verify_range(_First1, _Last1); _Adl_verify_range(_First2, _Last2); - auto _UFirst1 = _Get_unwrapped(_STD move(_First1)); - const auto _ULast1 = _Get_unwrapped(_Last1); - const auto _UFirst2 = _Get_unwrapped(_First2); - const auto _ULast2 = _Get_unwrapped(_Last2); - for (; _UFirst1 != _ULast1; ++_UFirst1) { - for (auto _UMid2 = _UFirst2; _UMid2 != _ULast2; ++_UMid2) { - if (_STD invoke(_Pred, _STD invoke(_Proj1, *_UFirst1), _STD invoke(_Proj2, *_UMid2))) { - _Seek_wrapped(_First1, _STD move(_UFirst1)); - return _First1; - } - } - } - _Seek_wrapped(_First1, _STD move(_UFirst1)); + auto _UResult = _Find_first_of_unchecked(_Get_unwrapped(_STD move(_First1)), + _Get_unwrapped(_STD move(_Last1)), _Get_unwrapped(_STD move(_First2)), + _Get_unwrapped(_STD move(_Last2)), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + + _Seek_wrapped(_First1, _STD move(_UResult)); return _First1; } @@ -3230,10 +3252,35 @@ namespace ranges { requires indirectly_comparable, iterator_t<_Rng2>, _Pr, _Pj1, _Pj2> _NODISCARD constexpr borrowed_iterator_t<_Rng1> operator()( _Rng1&& _Range1, _Rng2&& _Range2, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { - return (*this)(_RANGES begin(_Range1), _RANGES end(_Range1), _RANGES begin(_Range2), _RANGES end(_Range2), - _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + auto _First1 = _RANGES begin(_Range1); + + auto _UResult = _Find_first_of_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), + _Ubegin(_Range2), _Uend(_Range2), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + + _Seek_wrapped(_First1, _STD move(_UResult)); + return _First1; } // clang-format on + private: + template + _NODISCARD static constexpr _It1 _Find_first_of_unchecked(_It1 _First1, const _Se1 _Last1, const _It2 _First2, + const _Se2 _Last2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_comparable<_It1, _It2, _Pr, _Pj1, _Pj2>); + + for (; _First1 != _Last1; ++_First1) { + for (auto _Mid2 = _First2; _Mid2 != _Last2; ++_Mid2) { + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_Mid2))) { + return _First1; + } + } + } + + return _First1; + } }; inline constexpr _Find_first_of_fn find_first_of{_Not_quite_object::_Construct_tag{}}; diff --git a/tests/std/tests/P0896R4_ranges_alg_find/test.cpp b/tests/std/tests/P0896R4_ranges_alg_find/test.cpp index 575f1815438..768de98b7fc 100644 --- a/tests/std/tests/P0896R4_ranges_alg_find/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_find/test.cpp @@ -2,71 +2,56 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include #include #include #include #include +using namespace std; +using P = pair; -constexpr void smoke_test() { - using ranges::find, ranges::iterator_t, std::same_as; - using P = std::pair; +// Validate dangling story +STATIC_ASSERT(same_as{}, 42)), ranges::dangling>); +STATIC_ASSERT(same_as{}, 42)), int*>); - // Validate dangling story - STATIC_ASSERT(same_as{}, 42)), ranges::dangling>); - STATIC_ASSERT(same_as{}, 42)), int*>); - - std::array const pairs = {{{0, 42}, {2, 42}, {4, 42}}}; - - for (auto [value, _] : pairs) { - { - // Validate range overload [found case] - auto result = find(basic_borrowed_range{pairs}, value, get_first); - STATIC_ASSERT(same_as>>); - assert((*result).first == value); +struct instantiator { + static constexpr P haystack[3] = {{0, 42}, {2, 42}, {4, 42}}; + + template + static constexpr void call() { + using ranges::find, ranges::iterator_t; + + for (const auto& [value, _] : haystack) { + { // Validate range overload [found case] + Read wrapped_input{haystack}; + auto result = find(wrapped_input, value, get_first); + STATIC_ASSERT(same_as>); + assert(result.peek()->first == value); + } + { // Validate iterator + sentinel overload [found case] + Read wrapped_input{haystack}; + auto result = find(wrapped_input.begin(), wrapped_input.end(), value, get_first); + STATIC_ASSERT(same_as>); + assert(result.peek()->first == value); + } } - { - // Validate iterator + sentinel overload [found case] - basic_borrowed_range wrapped_pairs{pairs}; - auto result = find(wrapped_pairs.begin(), wrapped_pairs.end(), value, get_first); - STATIC_ASSERT(same_as>>); - assert((*result).first == value); + { // Validate range overload [not found case] + Read wrapped_input{haystack}; + auto result = find(wrapped_input, 42, get_first); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + } + { // Validate iterator + sentinel overload [not found case] + Read wrapped_input{haystack}; + auto result = find(wrapped_input.begin(), wrapped_input.end(), 42, get_first); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); } } - { - // Validate range overload [not found case] - auto result = find(basic_borrowed_range{pairs}, 42, get_first); - STATIC_ASSERT(same_as>>); - assert(result == basic_borrowed_range{pairs}.end()); - } - { - // Validate iterator + sentinel overload [not found case] - basic_borrowed_range wrapped_pairs{pairs}; - auto result = find(wrapped_pairs.begin(), wrapped_pairs.end(), 42, get_first); - STATIC_ASSERT(same_as>>); - assert(result == wrapped_pairs.end()); - } -} +}; int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); + STATIC_ASSERT((test_in(), true)); + test_in(); } - -struct instantiator { - template - static void call(In&& in = {}) { - ranges::range_value_t const value{}; - (void) ranges::find(in, value); - - struct type { - bool operator==(type const&) const = default; - }; - using Projection = type (*)(std::iter_common_reference_t>); - (void) ranges::find(in, type{}, Projection{}); - } -}; - -template void test_in(); diff --git a/tests/std/tests/P0896R4_ranges_alg_find_first_of/test.cpp b/tests/std/tests/P0896R4_ranges_alg_find_first_of/test.cpp index ef37646d8e0..7cde02634ea 100644 --- a/tests/std/tests/P0896R4_ranges_alg_find_first_of/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_find_first_of/test.cpp @@ -2,92 +2,88 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include #include #include #include #include +using namespace std; +using P = pair; -constexpr void smoke_test() { - using ranges::find_first_of, ranges::iterator_t, std::array, std::same_as, std::to_address; - using P = std::pair; +constexpr auto pred = [](const int x, const int y) { return x == y + 1; }; - // Validate dangling story - STATIC_ASSERT(same_as{}, array{})), ranges::dangling>); - STATIC_ASSERT(same_as{}, array{})), int*>); +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{}, pred)), ranges::dangling>); +STATIC_ASSERT(same_as{}, borrowed{}, pred)), int*>); - const array pairs = {P{0, 42}, P{1, 42}, P{2, 42}, P{3, 42}, P{4, 42}, P{5, 42}, P{6, 42}}; +struct instantiator { + static constexpr P haystack[7] = {{0, 42}, {1, 42}, {2, 42}, {3, 42}, {4, 42}, {5, 42}, {6, 42}}; + static constexpr int good_needle[2] = {29, 1}; + static constexpr int bad_needle[2] = {29, 17}; - const auto pred = [](const int x, const int y) { return x == y + 1; }; + template + static constexpr void call() { + using ranges::find_first_of, ranges::iterator_t; - const array good_needle = {29, 1}; - { - // Validate range overload [found case] - auto result = find_first_of(basic_borrowed_range{pairs}, good_needle, pred, get_first); - STATIC_ASSERT(same_as>>); - assert(result.peek() == pairs.data() + 2); - } - { - // Validate iterator + sentinel overload [found case] - basic_borrowed_range wrapped_pairs{pairs}; - auto result = find_first_of( - wrapped_pairs.begin(), wrapped_pairs.end(), good_needle.begin(), good_needle.end(), pred, get_first); - STATIC_ASSERT(same_as>>); - assert(result.peek() == pairs.data() + 2); - } + { // Validate range overload [found case] + Read1 wrapped_haystack{haystack}; + Read2 wrapped_needle{good_needle}; - const array bad_needle = {29, 17}; - { - // Validate range overload [not found case] - auto result = find_first_of(basic_borrowed_range{pairs}, bad_needle, pred, get_first); - STATIC_ASSERT(same_as>>); - assert(result.peek() == pairs.data() + pairs.size()); - } - { - // Validate iterator + sentinel overload [not found case] - basic_borrowed_range wrapped_pairs{pairs}; - auto result = find_first_of( - wrapped_pairs.begin(), wrapped_pairs.end(), bad_needle.begin(), bad_needle.end(), pred, get_first); - STATIC_ASSERT(same_as>>); - assert(result.peek() == pairs.data() + pairs.size()); - } -} - -int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); -} - -struct instantiator { - template - static void call(In1&& in1 = {}, Fwd2&& fwd2 = {}) { - if constexpr (!is_permissive) { // These fail to compile in C1XX's permissive mode due to VSO-566808 - using ranges::iterator_t; + auto result = find_first_of(wrapped_haystack, wrapped_needle, pred, get_first); + STATIC_ASSERT(same_as>); + assert(result.peek() == begin(haystack) + 2); + } + { // Validate iterator + sentinel overload [found case] + Read1 wrapped_haystack{haystack}; + Read2 wrapped_needle{good_needle}; - (void) ranges::find_first_of(in1, fwd2); - (void) ranges::find_first_of(ranges::begin(in1), ranges::end(in1), ranges::begin(fwd2), ranges::end(fwd2)); + auto result = find_first_of(wrapped_haystack.begin(), wrapped_haystack.end(), wrapped_needle.begin(), + wrapped_needle.end(), pred, get_first); + STATIC_ASSERT(same_as>); + assert(result.peek() == begin(haystack) + 2); + } - BinaryPredicateFor, iterator_t> pred{}; - (void) ranges::find_first_of(in1, fwd2, pred); - (void) ranges::find_first_of( - ranges::begin(in1), ranges::end(in1), ranges::begin(fwd2), ranges::end(fwd2), pred); + { // Validate range overload [not found case] + Read1 wrapped_haystack{haystack}; + Read2 wrapped_needle{bad_needle}; - HalfProjectedBinaryPredicateFor> halfpred{}; - ProjectionFor> halfproj{}; - (void) ranges::find_first_of(in1, fwd2, halfpred, halfproj); - (void) ranges::find_first_of( - ranges::begin(in1), ranges::end(in1), ranges::begin(fwd2), ranges::end(fwd2), halfpred, halfproj); + auto result = find_first_of(wrapped_haystack, wrapped_needle, pred, get_first); + STATIC_ASSERT(same_as>); + assert(result == wrapped_haystack.end()); + } + { + // Validate iterator + sentinel overload [not found case] + Read1 wrapped_haystack{haystack}; + Read2 wrapped_needle{bad_needle}; - ProjectedBinaryPredicate<0, 1> projpred{}; - ProjectionFor, 0> proj1{}; - ProjectionFor, 1> proj2{}; - (void) ranges::find_first_of(in1, fwd2, projpred, proj1, proj2); - (void) ranges::find_first_of( - ranges::begin(in1), ranges::end(in1), ranges::begin(fwd2), ranges::end(fwd2), projpred, proj1, proj2); + auto result = find_first_of(wrapped_haystack.begin(), wrapped_haystack.end(), wrapped_needle.begin(), + wrapped_needle.end(), pred, get_first); + STATIC_ASSERT(same_as>); + assert(result == wrapped_haystack.end()); } } }; -template void test_in_fwd(); +#ifdef TEST_EVERYTHING +int main() { + STATIC_ASSERT((test_in_fwd(), true)); + test_in_fwd(); +} +#else // ^^^ test all range combinations // test only interesting range combos vvv +constexpr bool run_tests() { + // The algorithm is oblivious to anything except maybe proxies so take the bare minimum input/forward range + using in_test_range = test::range; + using fwd_test_range = test::range; + + instantiator::call(); + return true; +} + +int main() { + STATIC_ASSERT(run_tests()); + run_tests(); +} +#endif // TEST_EVERYTHING diff --git a/tests/std/tests/P0896R4_ranges_alg_find_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_find_if/test.cpp index 5585b71a8ae..2737297b537 100644 --- a/tests/std/tests/P0896R4_ranges_alg_find_if/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_find_if/test.cpp @@ -2,69 +2,59 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include #include #include #include #include +using namespace std; +using P = pair; -constexpr void smoke_test() { - using ranges::find_if, ranges::iterator_t, std::same_as; - using P = std::pair; +constexpr auto matches = [](const int val) { return val == 42; }; +constexpr auto equals = [](auto x) { return [x](auto&& y) { return y == x; }; }; - std::array const pairs = {{{0, 42}, {2, 42}, {4, 42}}}; - constexpr auto equals = [](auto x) { return [x](auto&& y) { return y == x; }; }; +// Validate dangling story +STATIC_ASSERT(same_as{}, matches)), ranges::dangling>); +STATIC_ASSERT(same_as{}, matches)), int*>); - // Validate dangling story - STATIC_ASSERT(same_as{}, equals(42))), ranges::dangling>); - STATIC_ASSERT(same_as{}, equals(42))), int*>); - - for (auto [value, _] : pairs) { - { - // Validate range overload [found case] - auto result = find_if(basic_borrowed_range{pairs}, equals(value), get_first); - STATIC_ASSERT(same_as>>); - assert((*result).first == value); +struct instantiator { + static constexpr P haystack[3] = {{0, 42}, {2, 42}, {4, 42}}; + + template + static constexpr void call() { + using ranges::find_if, ranges::iterator_t; + + for (const auto& [value, _] : haystack) { + { // Validate range overload [found case] + Read wrapped_input{haystack}; + auto result = find_if(wrapped_input, equals(value), get_first); + STATIC_ASSERT(same_as>); + assert(result.peek()->first == value); + } + { // Validate iterator + sentinel overload [found case] + Read wrapped_input{haystack}; + auto result = find_if(wrapped_input.begin(), wrapped_input.end(), equals(value), get_first); + STATIC_ASSERT(same_as>); + assert(result.peek()->first == value); + } } - { - // Validate iterator + sentinel overload [found case] - basic_borrowed_range wrapped_pairs{pairs}; - auto result = find_if(wrapped_pairs.begin(), wrapped_pairs.end(), equals(value), get_first); - STATIC_ASSERT(same_as>>); - assert((*result).first == value); + { // Validate range overload [not found case] + Read wrapped_input{haystack}; + auto result = find_if(wrapped_input, equals(42), get_first); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + } + { // Validate iterator + sentinel overload [not found case] + Read wrapped_input{haystack}; + auto result = find_if(wrapped_input.begin(), wrapped_input.end(), equals(42), get_first); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); } } - { - // Validate range overload [not found case] - auto result = find_if(basic_borrowed_range{pairs}, equals(42), get_first); - STATIC_ASSERT(same_as>>); - assert(result == basic_borrowed_range{pairs}.end()); - } - { - // Validate iterator + sentinel overload [not found case] - basic_borrowed_range wrapped_pairs{pairs}; - auto result = find_if(wrapped_pairs.begin(), wrapped_pairs.end(), equals(42), get_first); - STATIC_ASSERT(same_as>>); - assert(result == wrapped_pairs.end()); - } -} +}; int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); + STATIC_ASSERT((test_in(), true)); + test_in(); } - -struct instantiator { - template - static void call(In&& in = {}) { - using ranges::iterator_t; - using I = iterator_t; - - (void) ranges::find_if(in, UnaryPredicateFor{}); - (void) ranges::find_if(in, ProjectedUnaryPredicate<>{}, ProjectionFor{}); - } -}; - -template void test_in(); diff --git a/tests/std/tests/P0896R4_ranges_alg_find_if_not/test.cpp b/tests/std/tests/P0896R4_ranges_alg_find_if_not/test.cpp index a75bf6829cd..f3ef5f5ded9 100644 --- a/tests/std/tests/P0896R4_ranges_alg_find_if_not/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_find_if_not/test.cpp @@ -9,64 +9,54 @@ #include #include +using namespace std; +using P = pair; -constexpr void smoke_test() { - using ranges::find_if_not, ranges::iterator_t, std::same_as; - using P = std::pair; +constexpr auto matches = [](const int val) { return val == 42; }; +constexpr auto equals = [](auto x) { return [x](auto&& y) { return y == x; }; }; - std::array pairs = {{{0, 13}, {0, 13}, {0, 13}}}; - constexpr auto equals = [](auto x) { return [x](auto&& y) { return y == x; }; }; +// Validate dangling story +STATIC_ASSERT(same_as{}, matches)), ranges::dangling>); +STATIC_ASSERT(same_as{}, matches)), int*>); - // Validate dangling story - STATIC_ASSERT(same_as{}, equals(42))), ranges::dangling>); - STATIC_ASSERT(same_as{}, equals(42))), int*>); - - for (auto& [value, _] : pairs) { - value = 42; - { - // Validate range overload [found case] - auto result = find_if_not(basic_borrowed_range{pairs}, equals(0), get_first); - STATIC_ASSERT(same_as>>); - assert((*result).first == 42); +struct instantiator { + template + static constexpr void call() { + using ranges::find_if_not, ranges::iterator_t; + P haystack[3] = {{0, 13}, {0, 13}, {0, 13}}; + + for (auto& [value, _] : haystack) { + value = 42; + { // Validate range overload [found case] + Read wrapped_input{haystack}; + auto result = find_if_not(wrapped_input, equals(0), get_first); + STATIC_ASSERT(same_as>); + assert(result.peek()->first == 42); + } + { // Validate iterator + sentinel overload [found case] + Read wrapped_input{haystack}; + auto result = find_if_not(wrapped_input.begin(), wrapped_input.end(), equals(0), get_first); + STATIC_ASSERT(same_as>); + assert(result.peek()->first == 42); + } + value = 0; } - { - // Validate iterator + sentinel overload [found case] - basic_borrowed_range wrapped_pairs{pairs}; - auto result = find_if_not(wrapped_pairs.begin(), wrapped_pairs.end(), equals(0), get_first); - STATIC_ASSERT(same_as>>); - assert((*result).first == 42); + { // Validate range overload [not found case] + Read wrapped_input{haystack}; + auto result = find_if_not(wrapped_input, equals(13), get_second); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); + } + { // Validate iterator + sentinel overload [not found case] + Read wrapped_input{haystack}; + auto result = find_if_not(wrapped_input.begin(), wrapped_input.end(), equals(13), get_second); + STATIC_ASSERT(same_as>); + assert(result == wrapped_input.end()); } - value = 0; - } - { - // Validate range overload [not found case] - auto result = find_if_not(basic_borrowed_range{pairs}, equals(0), get_first); - STATIC_ASSERT(same_as>>); - assert(result == basic_borrowed_range{pairs}.end()); - } - { - // Validate iterator + sentinel overload [not found case] - basic_borrowed_range wrapped_pairs{pairs}; - auto result = find_if_not(wrapped_pairs.begin(), wrapped_pairs.end(), equals(0), get_first); - STATIC_ASSERT(same_as>>); - assert(result == wrapped_pairs.end()); } -} +}; int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); + STATIC_ASSERT((test_in(), true)); + test_in(); } - -struct instantiator { - template - static void call(In&& in = {}) { - using ranges::iterator_t; - using I = iterator_t; - - (void) ranges::find_if_not(in, UnaryPredicateFor{}); - (void) ranges::find_if_not(in, ProjectedUnaryPredicate<>{}, ProjectionFor{}); - } -}; - -template void test_in(); From 5ada43e908262980462f05c37254309e74e2b109 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Mon, 27 Jul 2020 20:33:40 +0200 Subject: [PATCH 14/50] Modernize ranges::for_each (#1060) --- stl/inc/algorithm | 35 +++++-- .../P0896R4_ranges_alg_for_each/test.cpp | 98 +++++++++---------- .../P0896R4_ranges_alg_for_each_n/test.cpp | 57 +++++------ 3 files changed, 94 insertions(+), 96 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 61b41ec854f..07dcc745f76 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -356,21 +356,40 @@ namespace ranges { indirectly_unary_invocable> _Fn> constexpr for_each_result<_It, _Fn> operator()(_It _First, _Se _Last, _Fn _Func, _Pj _Proj = {}) const { _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_STD move(_First)); - const auto _ULast = _Get_unwrapped(_STD move(_Last)); - for (; _UFirst != _ULast; ++_UFirst) { - _STD invoke(_Func, _STD invoke(_Proj, *_UFirst)); - } - _Seek_wrapped(_First, _STD move(_UFirst)); - return {_STD move(_First), _STD move(_Func)}; + auto _UResult = _For_each_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _STD move(_Func), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.fun)}; } template , _Pj>> _Fn> constexpr for_each_result, _Fn> operator()( _Rng&& _Range, _Fn _Func, _Pj _Proj = {}) const { - return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _STD move(_Func), _Pass_fn(_Proj)); + auto _First = _RANGES begin(_Range); + + auto _UResult = _For_each_unchecked( + _Get_unwrapped(_STD move(_First)), _Uend(_Range), _STD move(_Func), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.fun)}; + } + + private: + template + _NODISCARD static constexpr for_each_result<_It, _Fn> _For_each_unchecked( + _It _First, const _Se _Last, _Fn _Func, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_unary_invocable<_Fn, projected<_It, _Pj>>); + + for (; _First != _Last; ++_First) { + _STD invoke(_Func, _STD invoke(_Proj, *_First)); + } + + return {_STD move(_First), _STD move(_Func)}; } }; diff --git a/tests/std/tests/P0896R4_ranges_alg_for_each/test.cpp b/tests/std/tests/P0896R4_ranges_alg_for_each/test.cpp index 5732d181cc9..076f1fc4c0e 100644 --- a/tests/std/tests/P0896R4_ranges_alg_for_each/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_for_each/test.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include #include #include @@ -10,66 +9,59 @@ #include #include +using namespace std; +using P = pair; -constexpr void smoke_test() { - using ranges::for_each, ranges::for_each_result, ranges::in_fun_result, ranges::iterator_t; - using std::identity, std::same_as; - using P = std::pair; - using R = std::array; +constexpr auto incr = [](auto& y) { ++y; }; - // Validate that for_each_result aliases in_fun_result - STATIC_ASSERT(same_as, in_fun_result>); +// Validate that for_each_result aliases in_fun_result +STATIC_ASSERT(same_as, ranges::in_fun_result>); - // Validate dangling story - STATIC_ASSERT( - same_as{}, identity{})), for_each_result>); - STATIC_ASSERT(same_as{}, identity{})), for_each_result>); +// Validate dangling story +STATIC_ASSERT(same_as{}, identity{})), + ranges::for_each_result>); +STATIC_ASSERT( + same_as{}, identity{})), ranges::for_each_result>); - R const values = {{{0, 42}, {2, 42}, {4, 42}}}; - auto incr = [](auto& y) { ++y; }; +struct instantiator { + static constexpr P expected[3] = {{1, 42}, {3, 42}, {5, 42}}; - { - auto pairs = values; - auto result = for_each(basic_borrowed_range{pairs}, incr, get_first); - STATIC_ASSERT(same_as>, decltype(incr)>>); - assert(result.in == basic_borrowed_range{pairs}.end()); - int some_value = 1729; - result.fun(some_value); - assert(some_value == 1730); - R const expected = {{{1, 42}, {3, 42}, {5, 42}}}; - assert(ranges::equal(pairs, expected)); - } - { - auto pairs = values; - basic_borrowed_range wrapped_pairs{pairs}; - auto result = for_each(wrapped_pairs.begin(), wrapped_pairs.end(), incr, get_second); - STATIC_ASSERT(same_as>, decltype(incr)>>); - assert(result.in == wrapped_pairs.end()); - int some_value = 1729; - result.fun(some_value); - assert(some_value == 1730); - R const expected = {{{0, 43}, {2, 43}, {4, 43}}}; - assert(ranges::equal(pairs, expected)); - } -} + template + static constexpr void call() { + using ranges::for_each, ranges::for_each_result, ranges::iterator_t; -int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); -} + { // Validate iterator + sentinel overload + P input[3] = {{0, 42}, {2, 42}, {4, 42}}; + ReadWrite wrapped_input{input}; -struct instantiator { - template - static void call(In&& in = {}) { - using I = ranges::iterator_t; - using Fun = void (*)(std::iter_common_reference_t); - ranges::for_each(in, Fun{}); - ranges::for_each(ranges::begin(in), ranges::end(in), Fun{}); + auto result = for_each(wrapped_input.begin(), wrapped_input.end(), incr, get_first); + STATIC_ASSERT( + same_as, remove_const_t>>); + assert(result.in == wrapped_input.end()); + assert(ranges::equal(expected, input)); + + int some_value = 1729; + result.fun(some_value); + assert(some_value == 1730); + } + { // Validate range overload + P input[3] = {{0, 42}, {2, 42}, {4, 42}}; + ReadWrite wrapped_input{input}; + + auto result = for_each(wrapped_input, incr, get_first); + STATIC_ASSERT( + same_as, remove_const_t>>); + assert(result.in == wrapped_input.end()); + assert(ranges::equal(expected, input)); - using ProjFun = void (*)(unique_tag<0>); - ranges::for_each(in, ProjFun{}, ProjectionFor{}); - ranges::for_each(ranges::begin(in), ranges::end(in), ProjFun{}, ProjectionFor{}); + int some_value = 1729; + result.fun(some_value); + assert(some_value == 1730); + } } }; -template void test_in(); +int main() { + STATIC_ASSERT((test_in(), true)); + test_in(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_for_each_n/test.cpp b/tests/std/tests/P0896R4_ranges_alg_for_each_n/test.cpp index 8f0cad3e233..068156df664 100644 --- a/tests/std/tests/P0896R4_ranges_alg_for_each_n/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_for_each_n/test.cpp @@ -2,53 +2,40 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include #include #include #include #include +using namespace std; +using P = pair; -constexpr void smoke_test() { - using ranges::for_each_n, ranges::for_each_n_result, ranges::in_fun_result, ranges::iterator_t; - using std::same_as; - using P = std::pair; - using R = std::array; - - // Validate that for_each_n_result aliases in_fun_result - STATIC_ASSERT(same_as, in_fun_result>); - - R pairs = {{{0, 42}, {2, 42}, {4, 42}}}; - auto incr = [](auto& y) { ++y; }; - - basic_borrowed_range wrapped_pairs{pairs}; - auto result = for_each_n(wrapped_pairs.begin(), ranges::distance(pairs), incr, get_first); - STATIC_ASSERT(same_as>, decltype(incr)>>); - assert(result.in == wrapped_pairs.end()); - int some_value = 1729; - result.fun(some_value); - assert(some_value == 1730); - R const expected = {{{1, 42}, {3, 42}, {5, 42}}}; - assert(ranges::equal(pairs, expected)); -} +constexpr auto incr = [](auto& y) { ++y; }; -int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); -} +// Validate that for_each_n_result aliases in_fun_result +STATIC_ASSERT(same_as, ranges::in_fun_result>); struct instantiator { - template - static void call(In in = {}) { - using std::iter_difference_t; + static constexpr P expected[3] = {{1, 42}, {3, 42}, {5, 42}}; - using Fun = void (*)(std::iter_common_reference_t); - ranges::for_each_n(std::move(in), iter_difference_t{}, Fun{}); + template ReadWrite> + static constexpr void call() { + using ranges::for_each_n, ranges::for_each_n_result, ranges::iterator_t, ranges::distance; + P input[3] = {{0, 42}, {2, 42}, {4, 42}}; - using ProjFun = void (*)(unique_tag<0>); - ranges::for_each_n(std::move(in), iter_difference_t{}, ProjFun{}, ProjectionFor{}); + auto result = for_each_n(ReadWrite{input}, distance(input), incr, get_first); + STATIC_ASSERT(same_as>>); + assert(result.in.peek() == end(input)); + assert(ranges::equal(expected, input)); + + int some_value = 1729; + result.fun(some_value); + assert(some_value == 1730); } }; -template void test_read(); +int main() { + STATIC_ASSERT((test_read(), true)); + test_read(); +} From fc3df2f5b1f56034c54a8a90e3939b91c6a4e66d Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Mon, 27 Jul 2020 20:34:50 +0200 Subject: [PATCH 15/50] Modernize ranges::all_of and friends (#1061) --- stl/inc/algorithm | 84 +++++++++++++------ .../tests/P0896R4_ranges_alg_all_of/test.cpp | 77 +++++++++-------- .../tests/P0896R4_ranges_alg_any_of/test.cpp | 75 +++++++++-------- .../tests/P0896R4_ranges_alg_none_of/test.cpp | 75 +++++++++-------- 4 files changed, 171 insertions(+), 140 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 07dcc745f76..6b93938c314 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1486,21 +1486,31 @@ namespace ranges { indirect_unary_predicate> _Pr> _NODISCARD constexpr bool operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_STD move(_First)); - const auto _ULast = _Get_unwrapped(_STD move(_Last)); - for (; _UFirst != _ULast; ++_UFirst) { - if (!_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst))) { - return false; - } - } - return true; + return _All_of_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); } template , _Pj>> _Pr> _NODISCARD constexpr bool operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { - return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _All_of_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + } + + private: + template + _NODISCARD static constexpr bool _All_of_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + + for (; _First != _Last; ++_First) { + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + return false; + } + } + + return true; } }; @@ -1540,21 +1550,31 @@ namespace ranges { indirect_unary_predicate> _Pr> _NODISCARD constexpr bool operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_STD move(_First)); - const auto _ULast = _Get_unwrapped(_STD move(_Last)); - for (; _UFirst != _ULast; ++_UFirst) { - if (_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst))) { - return true; - } - } - return false; + return _Any_of_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); } template , _Pj>> _Pr> _NODISCARD constexpr bool operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { - return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _Any_of_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + } + + private: + template + _NODISCARD static constexpr bool _Any_of_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + + for (; _First != _Last; ++_First) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + return true; + } + } + + return false; } }; @@ -1594,21 +1614,31 @@ namespace ranges { indirect_unary_predicate> _Pr> _NODISCARD constexpr bool operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_STD move(_First)); - const auto _ULast = _Get_unwrapped(_STD move(_Last)); - for (; _UFirst != _ULast; ++_UFirst) { - if (_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst))) { - return false; - } - } - return true; + return _None_of_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); } template , _Pj>> _Pr> _NODISCARD constexpr bool operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { - return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _None_of_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + } + + private: + template + _NODISCARD static constexpr bool _None_of_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + + for (; _First != _Last; ++_First) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + return false; + } + } + + return true; } }; diff --git a/tests/std/tests/P0896R4_ranges_alg_all_of/test.cpp b/tests/std/tests/P0896R4_ranges_alg_all_of/test.cpp index 27459ca6313..9b365039d66 100644 --- a/tests/std/tests/P0896R4_ranges_alg_all_of/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_all_of/test.cpp @@ -2,55 +2,54 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include +#include #include #include #include +using namespace std; +using P = pair; constexpr auto is_even = [](auto const& x) { return x % 2 == 0; }; constexpr auto is_odd = [](auto const& x) { return x % 2 != 0; }; -using R = std::array, 3>; - -constexpr void smoke_test() { - using ranges::all_of; - constexpr R pairs = {{{0, 13}, {2, 13}, {4, 13}}}; - - assert(all_of(basic_borrowed_range{pairs}, is_even, get_first)); - assert(!all_of(basic_borrowed_range{pairs}, is_even, get_second)); - assert(!all_of(basic_borrowed_range{pairs}, is_odd, get_first)); - assert(all_of(basic_borrowed_range{pairs}, is_odd, get_second)); - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(all_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_even, get_first)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(!all_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_even, get_second)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(!all_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_odd, get_first)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(all_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_odd, get_second)); - } -} - -int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); -} - struct instantiator { - template - static void call(In&& in = {}) { - (void) ranges::all_of(in, UnaryPredicateFor>{}); - (void) ranges::all_of(in, ProjectedUnaryPredicate<>{}, ProjectionFor>{}); + static constexpr P input[3] = {{0, 13}, {2, 13}, {4, 13}}; + + template + static constexpr void call() { + using ranges::all_of; + { // Validate iterator + sentinel overload + Read wrapped_input1{input}; + Read wrapped_input2{input}; + Read wrapped_input3{input}; + Read wrapped_input4{input}; + + auto result = all_of(wrapped_input1.begin(), wrapped_input1.end(), is_even, get_first); + STATIC_ASSERT(same_as); + assert(result); + assert(!all_of(wrapped_input2.begin(), wrapped_input2.end(), is_even, get_second)); + assert(!all_of(wrapped_input3.begin(), wrapped_input3.end(), is_odd, get_first)); + assert(all_of(wrapped_input4.begin(), wrapped_input4.end(), is_odd, get_second)); + } + { // Validate range overload + Read wrapped_input1{input}; + Read wrapped_input2{input}; + Read wrapped_input3{input}; + Read wrapped_input4{input}; + + auto result = all_of(wrapped_input1, is_even, get_first); + STATIC_ASSERT(same_as); + assert(result); + assert(!all_of(wrapped_input2, is_even, get_second)); + assert(!all_of(wrapped_input3, is_odd, get_first)); + assert(all_of(wrapped_input4, is_odd, get_second)); + } } }; -template void test_in(); +int main() { + STATIC_ASSERT((test_in(), true)); + test_in(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_any_of/test.cpp b/tests/std/tests/P0896R4_ranges_alg_any_of/test.cpp index 37a5afff2a1..4d872dc51a9 100644 --- a/tests/std/tests/P0896R4_ranges_alg_any_of/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_any_of/test.cpp @@ -2,53 +2,54 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include +#include #include #include #include +using namespace std; +using P = pair; constexpr auto is_even = [](auto const& x) { return x % 2 == 0; }; constexpr auto is_odd = [](auto const& x) { return x % 2 != 0; }; -constexpr void smoke_test() { - using ranges::any_of; - constexpr std::array, 3> pairs = {{{0, 13}, {7, 13}, {4, 13}}}; - - assert(any_of(basic_borrowed_range{pairs}, is_even, get_first)); - assert(!any_of(basic_borrowed_range{pairs}, is_even, get_second)); - assert(any_of(basic_borrowed_range{pairs}, is_odd, get_first)); - assert(any_of(basic_borrowed_range{pairs}, is_odd, get_second)); - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(any_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_even, get_first)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(!any_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_even, get_second)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(any_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_odd, get_first)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(any_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_odd, get_second)); - } -} - -int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); -} - struct instantiator { - template - static void call(In&& in = {}) { - (void) ranges::any_of(in, UnaryPredicateFor>{}); - (void) ranges::any_of(in, ProjectedUnaryPredicate<>{}, ProjectionFor>{}); + static constexpr P input[3] = {{0, 13}, {7, 13}, {4, 13}}; + + template + static constexpr void call() { + using ranges::any_of; + { // Validate iterator + sentinel overload + Read wrapped_input1{input}; + Read wrapped_input2{input}; + Read wrapped_input3{input}; + Read wrapped_input4{input}; + + auto result = any_of(wrapped_input1.begin(), wrapped_input1.end(), is_even, get_first); + STATIC_ASSERT(same_as); + assert(result); + assert(!any_of(wrapped_input2.begin(), wrapped_input2.end(), is_even, get_second)); + assert(any_of(wrapped_input3.begin(), wrapped_input3.end(), is_odd, get_first)); + assert(any_of(wrapped_input4.begin(), wrapped_input4.end(), is_odd, get_second)); + } + { // Validate range overload + Read wrapped_input1{input}; + Read wrapped_input2{input}; + Read wrapped_input3{input}; + Read wrapped_input4{input}; + + auto result = any_of(wrapped_input1, is_even, get_first); + STATIC_ASSERT(same_as); + assert(result); + assert(!any_of(wrapped_input2, is_even, get_second)); + assert(any_of(wrapped_input3, is_odd, get_first)); + assert(any_of(wrapped_input4, is_odd, get_second)); + } } }; -template void test_in(); +int main() { + STATIC_ASSERT((test_in(), true)); + test_in(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_none_of/test.cpp b/tests/std/tests/P0896R4_ranges_alg_none_of/test.cpp index 3717b1ae1b0..83f1da574af 100644 --- a/tests/std/tests/P0896R4_ranges_alg_none_of/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_none_of/test.cpp @@ -2,53 +2,54 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include +#include #include #include #include +using namespace std; +using P = pair; constexpr auto is_even = [](auto const& x) { return x % 2 == 0; }; constexpr auto is_odd = [](auto const& x) { return x % 2 != 0; }; -constexpr void smoke_test() { - using ranges::none_of; - constexpr std::array, 3> pairs = {{{0, 13}, {7, 13}, {4, 13}}}; - - assert(!none_of(basic_borrowed_range{pairs}, is_even, get_first)); - assert(none_of(basic_borrowed_range{pairs}, is_even, get_second)); - assert(!none_of(basic_borrowed_range{pairs}, is_odd, get_first)); - assert(!none_of(basic_borrowed_range{pairs}, is_odd, get_second)); - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(!none_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_even, get_first)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(none_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_even, get_second)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(!none_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_odd, get_first)); - } - { - basic_borrowed_range wrapped_pairs{pairs}; - assert(!none_of(wrapped_pairs.begin(), wrapped_pairs.end(), is_odd, get_second)); - } -} - -int main() { - STATIC_ASSERT((smoke_test(), true)); - smoke_test(); -} - struct instantiator { - template - static void call(In&& in = {}) { - (void) ranges::none_of(in, UnaryPredicateFor>{}); - (void) ranges::none_of(in, ProjectedUnaryPredicate<>{}, ProjectionFor>{}); + static constexpr P input[3] = {{0, 13}, {7, 13}, {4, 13}}; + + template + static constexpr void call() { + using ranges::none_of; + { // Validate iterator + sentinel overload + Read wrapped_input1{input}; + Read wrapped_input2{input}; + Read wrapped_input3{input}; + Read wrapped_input4{input}; + + auto result = none_of(wrapped_input1.begin(), wrapped_input1.end(), is_even, get_first); + STATIC_ASSERT(same_as); + assert(!result); + assert(none_of(wrapped_input2.begin(), wrapped_input2.end(), is_even, get_second)); + assert(!none_of(wrapped_input3.begin(), wrapped_input3.end(), is_odd, get_first)); + assert(!none_of(wrapped_input4.begin(), wrapped_input4.end(), is_odd, get_second)); + } + { // Validate range overload + Read wrapped_input1{input}; + Read wrapped_input2{input}; + Read wrapped_input3{input}; + Read wrapped_input4{input}; + + auto result = none_of(wrapped_input1, is_even, get_first); + STATIC_ASSERT(same_as); + assert(!result); + assert(none_of(wrapped_input2, is_even, get_second)); + assert(!none_of(wrapped_input3, is_odd, get_first)); + assert(!none_of(wrapped_input4, is_odd, get_second)); + } } }; -template void test_in(); +int main() { + STATIC_ASSERT((test_in(), true)); + test_in(); +} From b2f1556d2a0ad9a1ec0d764594972ce14c08a25c Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 27 Jul 2020 11:38:12 -0700 Subject: [PATCH 16/50] Update .clang-format for Clang 10 (#1075) * Remove unnecessary comments in ranges tests. We taught clang-format how to sort like this automatically. * Fix x86 compiler error in stljobs.h. This never compiled for x86 where size_t and SIZE_T are different types. * Update .clang-format for Clang 10. AllowShortBlocksOnASingleLine changed from false/true to Never/Empty/Always. We still want the default of Never, as Empty appears to misbehave. DeriveLineEnding was added, defaulting to true. We want to disable line ending auto-detection. SortPriority was added. Its documentation is difficult to understand, but it can be used to group includes together while modifying their otherwise-lexicographic ordering. Here, I'm using it to group WinSDK includes together, while still sorting WinIoCtl.h last. IncludeIsMainSourceRegex was added. This doesn't appear to be relevant to us. IndentGotoLabels was added. We've avoided goto. SpaceInEmptyBlock was added. We prefer the default of no space. SpacesInConditionalStatement was added. We definitely prefer the default of no space. SpaceBeforeSquareBrackets was added. Again, we definitely prefer the default of no space. The Standard option was overhauled. Previously, Cpp11 meant "use the latest supported standard". That was confusing, so it has been deprecated in favor of Latest. UseCRLF was added. We currently use CRLF for all files. (Previously, validate.cpp would detect LF files, but clang-format wouldn't fix them.) * In .clang-format, delete empty lines and move comment. The empty lines were intended to make it easier to see customized options, but they were actually making it harder to diff the default config against our config. Move the StatementMacros comment next to our customized settings, and rewrap it. * Reformat filesystem.cpp. --- .clang-format | 60 +++++++------------ stl/src/filesystem.cpp | 1 - .../test.cpp | 2 +- .../tests/P0896R4_ranges_alg_search/test.cpp | 2 +- .../P0896R4_ranges_range_machinery/test.cpp | 2 +- tools/inc/stljobs.h | 2 +- 6 files changed, 24 insertions(+), 45 deletions(-) diff --git a/.clang-format b/.clang-format index a5878f28342..5ea2651baa0 100644 --- a/.clang-format +++ b/.clang-format @@ -1,54 +1,40 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# https://releases.llvm.org/9.0.0/tools/clang/docs/ClangFormatStyleOptions.html +# https://releases.llvm.org/10.0.0/tools/clang/docs/ClangFormatStyleOptions.html --- # Language: Cpp - BasedOnStyle: LLVM - # AccessModifierOffset: -2 AccessModifierOffset: -4 - # AlignAfterOpenBracket: Align AlignAfterOpenBracket: DontAlign - # AlignConsecutiveMacros: false AlignConsecutiveMacros: true - # AlignConsecutiveAssignments: false AlignConsecutiveAssignments: true - # AlignConsecutiveDeclarations: false - # AlignEscapedNewlines: Right AlignEscapedNewlines: Left - # AlignOperands: true - # AlignTrailingComments: true AlignTrailingComments: false - # AllowAllArgumentsOnNextLine: true # AllowAllConstructorInitializersOnNextLine: true # AllowAllParametersOfDeclarationOnNextLine: true -# AllowShortBlocksOnASingleLine: false +# AllowShortBlocksOnASingleLine: Never # AllowShortCaseLabelsOnASingleLine: false - # AllowShortFunctionsOnASingleLine: All AllowShortFunctionsOnASingleLine: Empty - # AllowShortLambdasOnASingleLine: All # AllowShortIfStatementsOnASingleLine: Never # AllowShortLoopsOnASingleLine: false # AlwaysBreakAfterDefinitionReturnType: None # AlwaysBreakAfterReturnType: None # AlwaysBreakBeforeMultilineStrings: false - # AlwaysBreakTemplateDeclarations: MultiLine AlwaysBreakTemplateDeclarations: Yes - # BinPackArguments: true # BinPackParameters: true # BraceWrapping: @@ -68,10 +54,8 @@ AlwaysBreakTemplateDeclarations: Yes # SplitEmptyFunction: true # SplitEmptyRecord: true # SplitEmptyNamespace: true - # BreakBeforeBinaryOperators: None BreakBeforeBinaryOperators: NonAssignment - # BreakBeforeBraces: Attach # BreakBeforeInheritanceComma: false # BreakInheritanceList: BeforeColon @@ -80,16 +64,16 @@ BreakBeforeBinaryOperators: NonAssignment # BreakConstructorInitializers: BeforeColon # BreakAfterJavaFieldAnnotations: false # BreakStringLiterals: true - # ColumnLimit: 80 ColumnLimit: 120 - # CommentPragmas: '^ IWYU pragma:' # CompactNamespaces: false # ConstructorInitializerAllOnOneLineOrOnePerLine: false # ConstructorInitializerIndentWidth: 4 # ContinuationIndentWidth: 4 # Cpp11BracedListStyle: true +# DeriveLineEnding: true +DeriveLineEnding: false # DerivePointerAlignment: false # DisableFormat: false # ExperimentalAutoDetectBinPacking: false @@ -98,55 +82,52 @@ ColumnLimit: 120 # - foreach # - Q_FOREACH # - BOOST_FOREACH - # IncludeBlocks: Preserve IncludeBlocks: Regroup - # IncludeCategories: # - Regex: '^"(llvm|llvm-c|clang|clang-c)/' # Priority: 2 +# SortPriority: 0 # - Regex: '^(<|"(gtest|gmock|isl|json)/)' # Priority: 3 +# SortPriority: 0 # - Regex: '.*' # Priority: 1 +# SortPriority: 0 IncludeCategories: - Regex: '^$' Priority: 1 - Regex: '^<(Windows|userenv)\.h>$' Priority: 3 + SortPriority: 3 - Regex: '^$' - Priority: 4 + Priority: 3 + SortPriority: 4 - Regex: '^<__.*\.hpp>$' Priority: 2 - Regex: '\.hpp[>"]$' Priority: 5 - Regex: '.*' Priority: 2 - # IncludeIsMainRegex: '(Test)?$' +# IncludeIsMainSourceRegex: '' # IndentCaseLabels: false +# IndentGotoLabels: true # IndentPPDirectives: None - # IndentWidth: 2 IndentWidth: 4 - # IndentWrappedFunctionNames: false IndentWrappedFunctionNames: true - # JavaScriptQuotes: Leave # JavaScriptWrapImports: true # KeepEmptyLinesAtTheStartOfBlocks: true - # NOTE: MacroBlockBegin/MacroBlockEnd don't work with _CATCH_ALL. # MacroBlockBegin: '' # MacroBlockEnd: '' - # MaxEmptyLinesToKeep: 1 MaxEmptyLinesToKeep: 2 - # NamespaceIndentation: None NamespaceIndentation: All - # ObjCBinPackProtocolList: Auto # ObjCBlockIndentWidth: 2 # ObjCSpaceAfterProperty: false @@ -159,17 +140,13 @@ NamespaceIndentation: All # PenaltyBreakTemplateDeclaration: 10 # PenaltyExcessCharacter: 1000000 # PenaltyReturnTypeOnItsOwnLine: 60 - # PointerAlignment: Right PointerAlignment: Left - # ReflowComments: true # SortIncludes: true # SortUsingDeclarations: true - # SpaceAfterCStyleCast: false SpaceAfterCStyleCast: true - # SpaceAfterLogicalNot: false # SpaceAfterTemplateKeyword: true # SpaceBeforeAssignmentOperators: true @@ -178,20 +155,22 @@ SpaceAfterCStyleCast: true # SpaceBeforeInheritanceColon: true # SpaceBeforeParens: ControlStatements # SpaceBeforeRangeBasedForLoopColon: true +# SpaceInEmptyBlock: false # SpaceInEmptyParentheses: false # SpacesBeforeTrailingComments: 1 # SpacesInAngles: false +# SpacesInConditionalStatement: false # SpacesInContainerLiterals: true # SpacesInCStyleCastParentheses: false # SpacesInParentheses: false # SpacesInSquareBrackets: false -# Standard: Cpp11 - -# NOTE: _STD_BEGIN, _STD_END, etc. aren't macros for complete statements, but telling clang-format that they are -# produces the behavior that we want (with no block indentation). +# SpaceBeforeSquareBrackets: false +# Standard: Latest # StatementMacros: # - Q_UNUSED # - QT_REQUIRE_VERSION +# NOTE: _STD_BEGIN, _STD_END, etc. aren't macros for complete statements, but telling +# clang-format that they are produces the behavior that we want (with no block indentation). StatementMacros: - _STD_BEGIN - _STD_END @@ -201,7 +180,8 @@ StatementMacros: - _END_EXTERN_C - _EXTERN_C_UNLESS_PURE - _END_EXTERN_C_UNLESS_PURE - # TabWidth: 8 +# UseCRLF: false +UseCRLF: true # UseTab: Never ... diff --git a/stl/src/filesystem.cpp b/stl/src/filesystem.cpp index 0a1d3d5a1e8..063f5f9c7a6 100644 --- a/stl/src/filesystem.cpp +++ b/stl/src/filesystem.cpp @@ -18,7 +18,6 @@ #include #include - #include // We have several switches that do not have case statements for every possible enum value. diff --git a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp index 9cd422f5b6f..ecf2f498f1c 100644 --- a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp @@ -8,7 +8,7 @@ #include #include #include -// + #include using namespace std; diff --git a/tests/std/tests/P0896R4_ranges_alg_search/test.cpp b/tests/std/tests/P0896R4_ranges_alg_search/test.cpp index cc284d34379..09875c71996 100644 --- a/tests/std/tests/P0896R4_ranges_alg_search/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_search/test.cpp @@ -8,7 +8,7 @@ #include #include #include -// + #include using namespace std; diff --git a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp index f74ed9425d7..10e217d99ad 100644 --- a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp @@ -25,7 +25,7 @@ #include #include #include -// + #include // Note that many tests herein assume: diff --git a/tools/inc/stljobs.h b/tools/inc/stljobs.h index f0f82da6c03..9e494a86e14 100644 --- a/tools/inc/stljobs.h +++ b/tools/inc/stljobs.h @@ -501,7 +501,7 @@ class thread_proc_attribute_list { thread_proc_attribute_list& operator=(thread_proc_attribute_list&&) = default; explicit thread_proc_attribute_list(const unsigned long attributeCount) { - size_t size; + SIZE_T size; if (InitializeProcThreadAttributeList(nullptr, attributeCount, 0, &size)) { fputs("First call to InitializeProcThreadAttributeList should not succeed.", stderr); abort(); From f49ffafb5dd63b7d2f157016123080942b0a6676 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Mon, 27 Jul 2020 20:42:15 +0200 Subject: [PATCH 17/50] Uglify the current member of move_iterator (#1080) --- stl/inc/xutility | 76 ++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index ff8a7aa2d63..91b39c72cd9 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -3902,7 +3902,7 @@ public: _CONSTEXPR17 move_iterator() = default; _CONSTEXPR17 explicit move_iterator(_Iter _Right) noexcept(is_nothrow_move_constructible_v<_Iter>) // strengthened - : current(_STD move(_Right)) {} + : _Current(_STD move(_Right)) {} // clang-format off template @@ -3912,7 +3912,7 @@ public: #endif // __cpp_lib_concepts _CONSTEXPR17 move_iterator(const move_iterator<_Other>& _Right) noexcept( is_nothrow_constructible_v<_Iter, const _Other&>) // strengthened - : current(_Right.base()) {} + : _Current(_Right.base()) {} template #ifdef __cpp_lib_concepts @@ -3922,38 +3922,38 @@ public: #endif // __cpp_lib_concepts _CONSTEXPR17 move_iterator& operator=(const move_iterator<_Other>& _Right) noexcept( is_nothrow_assignable_v<_Iter&, const _Other&>) /* strengthened */ { - current = _Right.base(); + _Current = _Right.base(); return *this; } // clang-format on #ifdef __cpp_lib_concepts _NODISCARD constexpr const iterator_type& base() const& { // Per LWG-3391 - return current; + return _Current; } _NODISCARD constexpr iterator_type base() && { - return _STD move(current); + return _STD move(_Current); } #else // ^^^ __cpp_lib_concepts / !__cpp_lib_concepts vvv _NODISCARD _CONSTEXPR17 iterator_type base() const { - return current; + return _Current; } #endif // __cpp_lib_concepts _NODISCARD _CONSTEXPR17 reference operator*() const { #ifdef __cpp_lib_concepts - return _RANGES iter_move(current); + return _RANGES iter_move(_Current); #else // ^^^ __cpp_lib_concepts / !__cpp_lib_concepts vvv - return static_cast(*current); + return static_cast(*_Current); #endif // __cpp_lib_concepts } _NODISCARD _CXX20_DEPRECATE_MOVE_ITERATOR_ARROW _CONSTEXPR17 pointer operator->() const { - return current; + return _Current; } _CONSTEXPR17 move_iterator& operator++() { - ++current; + ++_Current; return *this; } @@ -3962,102 +3962,102 @@ public: if constexpr (forward_iterator<_Iter>) { #endif // __cpp_lib_concepts move_iterator _Tmp = *this; - ++current; + ++_Current; return _Tmp; #ifdef __cpp_lib_concepts } else { - ++current; + ++_Current; } #endif // __cpp_lib_concepts } _CONSTEXPR17 move_iterator& operator--() { - --current; + --_Current; return *this; } _CONSTEXPR17 move_iterator operator--(int) { move_iterator _Tmp = *this; - --current; + --_Current; return _Tmp; } template _NODISCARD auto operator==(_Default_sentinel _Sentinel) const noexcept -> decltype(_STD declval() == _Sentinel) { - return current == _Sentinel; + return _Current == _Sentinel; } template _NODISCARD auto operator!=(_Default_sentinel _Sentinel) const noexcept -> decltype(_STD declval() != _Sentinel) { - return current != _Sentinel; + return _Current != _Sentinel; } _NODISCARD _CONSTEXPR17 move_iterator operator+(const difference_type _Off) const { - return move_iterator(current + _Off); + return move_iterator(_Current + _Off); } _CONSTEXPR17 move_iterator& operator+=(const difference_type _Off) { - current += _Off; + _Current += _Off; return *this; } _NODISCARD _CONSTEXPR17 move_iterator operator-(const difference_type _Off) const { - return move_iterator(current - _Off); + return move_iterator(_Current - _Off); } _CONSTEXPR17 move_iterator& operator-=(const difference_type _Off) { - current -= _Off; + _Current -= _Off; return *this; } _NODISCARD _CONSTEXPR17 reference operator[](const difference_type _Off) const { #ifdef __cpp_lib_concepts - return _RANGES iter_move(current + _Off); + return _RANGES iter_move(_Current + _Off); #else // ^^^ __cpp_lib_concepts / !__cpp_lib_concepts vvv - return _STD move(current[_Off]); + return _STD move(_Current[_Off]); #endif // __cpp_lib_concepts } #ifdef __cpp_lib_concepts template _Sent> _NODISCARD friend constexpr bool operator==(const move_iterator& _Left, const move_sentinel<_Sent>& _Right) { - return _Left.current == _Right._Get_last(); + return _Left._Current == _Right._Get_last(); } template _Sent> _NODISCARD friend constexpr difference_type operator-( const move_sentinel<_Sent>& _Left, const move_iterator& _Right) { - return _Left._Get_last() - _Right.current; + return _Left._Get_last() - _Right._Current; } template _Sent> _NODISCARD friend constexpr difference_type operator-( const move_iterator& _Left, const move_sentinel<_Sent>& _Right) { - return _Left.current - _Right._Get_last(); + return _Left._Current - _Right._Get_last(); } _NODISCARD friend constexpr reference iter_move(const move_iterator& _It) noexcept( - noexcept(_RANGES iter_move(_It.current))) { - return _RANGES iter_move(_It.current); + noexcept(_RANGES iter_move(_It._Current))) { + return _RANGES iter_move(_It._Current); } template _Iter2> friend constexpr void iter_swap(const move_iterator& _Left, const move_iterator<_Iter2>& _Right) noexcept( - noexcept(_RANGES iter_swap(_Left.current, _Right.base()))) { - _RANGES iter_swap(_Left.current, _Right.base()); + noexcept(_RANGES iter_swap(_Left._Current, _Right.base()))) { + _RANGES iter_swap(_Left._Current, _Right.base()); } #endif // __cpp_lib_concepts template , int> = 0> friend constexpr void _Verify_range(const move_iterator& _First, const move_iterator<_Iter2>& _Last) { - _Verify_range(_First.current, _Last.base()); + _Verify_range(_First._Current, _Last.base()); } #ifdef __cpp_lib_concepts template _Sent, enable_if_t<_Range_verifiable_v<_Iter, _Sent>, int> = 0> friend constexpr void _Verify_range(const move_iterator& _First, const move_sentinel<_Sent>& _Last) { - _Verify_range(_First.current, _Last._Get_last()); + _Verify_range(_First._Current, _Last._Get_last()); } #endif // __cpp_lib_concepts @@ -4065,31 +4065,31 @@ public: template , int> = 0> constexpr void _Verify_offset(const difference_type _Off) const { - current._Verify_offset(_Off); + _Current._Verify_offset(_Off); } template , int> = 0> _NODISCARD constexpr move_iterator<_Unwrapped_t> _Unwrapped() const& { - return static_cast>>(current._Unwrapped()); + return static_cast>>(_Current._Unwrapped()); } template , int> = 0> _NODISCARD constexpr move_iterator<_Unwrapped_t<_Iter2>> _Unwrapped() && { - return static_cast>>(_STD move(current)._Unwrapped()); + return static_cast>>(_STD move(_Current)._Unwrapped()); } static constexpr bool _Unwrap_when_unverified = _Do_unwrap_when_unverified_v; template , int> = 0> constexpr void _Seek_to(const move_iterator<_Src>& _It) { - current._Seek_to(_It.base()); + _Current._Seek_to(_It.base()); } template , int> = 0> constexpr void _Seek_to(move_iterator<_Src>&& _It) { - current._Seek_to(_STD move(_It).base()); + _Current._Seek_to(_STD move(_It).base()); } -protected: - iterator_type current{}; +private: + iterator_type _Current{}; }; template From 3659f3dc1853cbd760c3b7379b48c2c7078ef711 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 28 Jul 2020 15:26:06 -0700 Subject: [PATCH 18/50] Implement Ranges Set Operations (#1044) Includes the `mergeable` concept and algorithms `ranges::includes`, `ranges::set_union`, `ranges::set_intersection`, `ranges::set_difference`, and `ranges::set_symmetric_difference`. --- stl/inc/algorithm | 406 ++++++++++++++++++ stl/inc/xutility | 10 +- tests/std/test.lst | 5 + .../tests/P0896R4_ranges_alg_includes/env.lst | 4 + .../P0896R4_ranges_alg_includes/test.cpp | 104 +++++ .../P0896R4_ranges_alg_set_difference/env.lst | 4 + .../test.cpp | 128 ++++++ .../env.lst | 4 + .../test.cpp | 146 +++++++ .../env.lst | 4 + .../test.cpp | 143 ++++++ .../P0896R4_ranges_alg_set_union/env.lst | 4 + .../P0896R4_ranges_alg_set_union/test.cpp | 139 ++++++ .../test.cpp | 129 ++++++ 14 files changed, 1229 insertions(+), 1 deletion(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_includes/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_includes/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_set_difference/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_set_difference/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_set_intersection/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_set_intersection/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_set_symmetric_difference/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_set_symmetric_difference/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_set_union/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_set_union/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 6b93938c314..eb8559309ac 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -7602,6 +7602,79 @@ _NODISCARD bool includes(_ExPo&&, _FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _Firs _REQUIRE_PARALLEL_ITERATOR(_FwdIt2); return _STD includes(_First1, _Last1, _First2, _Last2); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::includes + class _Includes_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se1, input_iterator _It2, sentinel_for<_It2> _Se2, + class _Pj1 = identity, class _Pj2 = identity, + indirect_strict_weak_order, projected<_It2, _Pj2>> _Pr = ranges::less> + _NODISCARD constexpr bool operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, _Pr _Pred = {}, + _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + return _Includes_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)), + _Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2)), _Pass_fn(_Pred), + _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + } + + template , _Pj1>, projected, _Pj2>> _Pr = + ranges::less> + _NODISCARD constexpr bool operator()( + _Rng1&& _Range1, _Rng2&& _Range2, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + return _Includes_unchecked(_Ubegin(_Range1), _Uend(_Range1), _Ubegin(_Range2), _Uend(_Range2), + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + } + + private: + template + _NODISCARD static constexpr bool _Includes_unchecked( + _It1 _First1, const _Se1 _Last1, _It2 _First2, const _Se2 _Last2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT(indirect_strict_weak_order<_Pr, projected<_It1, _Pj1>, projected<_It2, _Pj2>>); + + if (_First2 == _Last2) { + return true; + } else if (_First1 == _Last1) { + return false; + } + + for (;;) { + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { + ++_First1; + if (_First1 == _Last1) { + return false; + } + + continue; + } + + if (_STD invoke(_Pred, _STD invoke(_Proj2, *_First2), _STD invoke(_Proj1, *_First1))) { + return false; + } + + ++_First1; + ++_First2; + if (_First2 == _Last2) { + return true; + } else if (_First1 == _Last1) { + return false; + } + } + } + }; + + inline constexpr _Includes_fn includes{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE set_union @@ -7664,6 +7737,84 @@ _FwdIt3 set_union(_ExPo&&, _FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Fw _REQUIRE_PARALLEL_ITERATOR(_FwdIt3); return _STD set_union(_First1, _Last1, _First2, _Last2, _Dest); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE set_union_result + template + using set_union_result = in_in_out_result<_In1, _In2, _Out>; + + // VARIABLE ranges::set_union + class _Set_union_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se1, input_iterator _It2, sentinel_for<_It2> _Se2, + weakly_incrementable _Out, class _Pr = ranges::less, class _Pj1 = identity, class _Pj2 = identity> + requires mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2> + constexpr set_union_result<_It1, _It2, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, + _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UResult = _Set_union_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)), + _Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2)), + _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + + template + requires mergeable, iterator_t<_Rng2>, _Out, _Pr, _Pj1, _Pj2> + constexpr set_union_result, borrowed_iterator_t<_Rng2>, _Out> operator()( + _Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + auto _First1 = _RANGES begin(_Range1); + auto _First2 = _RANGES begin(_Range2); + auto _UResult = _Set_union_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), + _Get_unwrapped(_STD move(_First2)), _Uend(_Range2), _Get_unwrapped_unverified(_STD move(_Result)), + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + // clang-format on + private: + template + _NODISCARD static constexpr set_union_result<_It1, _It2, _Out> _Set_union_unchecked(_It1 _First1, + const _Se1 _Last1, _It2 _First2, const _Se2 _Last2, _Out _Result, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2>); + + for (; _First1 != _Last1 && _First2 != _Last2; ++_Result) { + if (_STD invoke(_Pred, _STD invoke(_Proj2, *_First2), _STD invoke(_Proj1, *_First1))) { + *_Result = *_First2; + ++_First2; + } else { + *_Result = *_First1; + if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { + ++_First2; + } + ++_First1; + } + } + + auto _UResult1 = _RANGES _Copy_unchecked(_STD move(_First1), _STD move(_Last1), _STD move(_Result)); + auto _UResult2 = _RANGES _Copy_unchecked(_STD move(_First2), _STD move(_Last2), _STD move(_UResult1.out)); + return {_STD move(_UResult1.in), _STD move(_UResult2.in), _STD move(_UResult2.out)}; + } + }; + + inline constexpr _Set_union_fn set_union{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE set_intersection @@ -7714,6 +7865,91 @@ _FwdIt3 set_intersection(_ExPo&& _Exec, _FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 // AND sets [_First1, _Last1) and [_First2, _Last2) return _STD set_intersection(_STD forward<_ExPo>(_Exec), _First1, _Last1, _First2, _Last2, _Dest, less{}); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE set_intersection_result + template + using set_intersection_result = in_in_out_result<_In1, _In2, _Out>; + + // VARIABLE ranges::set_intersection + class _Set_intersection_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se1, input_iterator _It2, sentinel_for<_It2> _Se2, + weakly_incrementable _Out, class _Pr = ranges::less, class _Pj1 = identity, class _Pj2 = identity> + requires mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2> + constexpr set_intersection_result<_It1, _It2, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, + _Se2 _Last2, _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UResult = + _Set_intersection_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)), + _Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2)), + _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + + template + requires mergeable, iterator_t<_Rng2>, _Out, _Pr, _Pj1, _Pj2> + constexpr set_intersection_result, borrowed_iterator_t<_Rng2>, _Out> operator()( + _Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + auto _First1 = _RANGES begin(_Range1); + auto _First2 = _RANGES begin(_Range2); + auto _UResult = _Set_intersection_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), + _Get_unwrapped(_STD move(_First2)), _Uend(_Range2), _Get_unwrapped_unverified(_STD move(_Result)), + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + // clang-format on + private: + template + _NODISCARD static constexpr set_intersection_result<_It1, _It2, _Out> _Set_intersection_unchecked(_It1 _First1, + const _Se1 _Last1, _It2 _First2, const _Se2 _Last2, _Out _Result, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2>); + + for (;;) { + if (_First1 == _Last1) { + _RANGES advance(_First2, _Last2); + break; + } else if (_First2 == _Last2) { + _RANGES advance(_First1, _Last1); + break; + } + + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { + ++_First1; + } else if (_STD invoke(_Pred, _STD invoke(_Proj2, *_First2), _STD invoke(_Proj1, *_First1))) { + ++_First2; + } else { + *_Result = *_First1; + ++_Result; + ++_First1; + ++_First2; + } + } + + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + }; + + inline constexpr _Set_intersection_fn set_intersection{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE set_difference @@ -7765,6 +8001,87 @@ _FwdIt3 set_difference(_ExPo&& _Exec, _FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _ // take set [_First2, _Last2) from [_First1, _Last1) return _STD set_difference(_STD forward<_ExPo>(_Exec), _First1, _Last1, _First2, _Last2, _Dest, less{}); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE set_difference_result + template + using set_difference_result = in_out_result<_In, _Out>; + + // VARIABLE ranges::set_difference + class _Set_difference_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se1, input_iterator _It2, sentinel_for<_It2> _Se2, + weakly_incrementable _Out, class _Pr = ranges::less, class _Pj1 = identity, class _Pj2 = identity> + requires mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2> + constexpr set_difference_result<_It1, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, + _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UResult = + _Set_difference_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)), + _Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2)), + _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + _Seek_wrapped(_First1, _STD move(_UResult.in)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_Result)}; + } + + template + requires mergeable, iterator_t<_Rng2>, _Out, _Pr, _Pj1, _Pj2> + constexpr set_difference_result, _Out> operator()( + _Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + auto _First1 = _RANGES begin(_Range1); + auto _UResult = _Set_difference_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), + _Ubegin(_Range2), _Uend(_Range2), _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), + _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + _Seek_wrapped(_First1, _STD move(_UResult.in)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_Result)}; + } + // clang-format on + private: + template + _NODISCARD static constexpr set_difference_result<_It1, _Out> _Set_difference_unchecked(_It1 _First1, + const _Se1 _Last1, _It2 _First2, const _Se2 _Last2, _Out _Result, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2>); + + for (;;) { + if (_First1 == _Last1) { + return {_STD move(_First1), _STD move(_Result)}; + } + + if (_First2 == _Last2) { + return _RANGES _Copy_unchecked(_STD move(_First1), _STD move(_Last1), _STD move(_Result)); + } + + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { + *_Result = *_First1; + ++_Result; + ++_First1; + } else { + if (!_STD invoke(_Pred, _STD invoke(_Proj2, *_First2), _STD invoke(_Proj1, *_First1))) { + ++_First1; + } + + ++_First2; + } + } + } + }; + + inline constexpr _Set_difference_fn set_difference{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE set_symmetric_difference @@ -7830,6 +8147,95 @@ _FwdIt3 set_symmetric_difference(_ExPo&&, _FwdIt1 _First1, _FwdIt1 _Last1, _FwdI _REQUIRE_PARALLEL_ITERATOR(_FwdIt3); return _STD set_symmetric_difference(_First1, _Last1, _First2, _Last2, _Dest); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE set_symmetric_difference_result + template + using set_symmetric_difference_result = in_in_out_result<_In1, _In2, _Out>; + + // VARIABLE ranges::set_symmetric_difference + class _Set_symmetric_difference_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se1, input_iterator _It2, sentinel_for<_It2> _Se2, + weakly_incrementable _Out, class _Pr = ranges::less, class _Pj1 = identity, class _Pj2 = identity> + requires mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2> + constexpr set_symmetric_difference_result<_It1, _It2, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, + _Se2 _Last2, _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UResult = _Set_symmetric_difference_unchecked(_Get_unwrapped(_STD move(_First1)), + _Get_unwrapped(_STD move(_Last1)), _Get_unwrapped(_STD move(_First2)), + _Get_unwrapped(_STD move(_Last2)), _Get_unwrapped_unverified(_STD move(_Result)), _Pass_fn(_Pred), + _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + + template + requires mergeable, iterator_t<_Rng2>, _Out, _Pr, _Pj1, _Pj2> + constexpr set_symmetric_difference_result, borrowed_iterator_t<_Rng2>, _Out> + operator()(_Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, + _Pj2 _Proj2 = {}) const { + auto _First1 = _RANGES begin(_Range1); + auto _First2 = _RANGES begin(_Range2); + auto _UResult = _Set_symmetric_difference_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), + _Get_unwrapped(_STD move(_First2)), _Uend(_Range2), _Get_unwrapped_unverified(_STD move(_Result)), + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + _Seek_wrapped(_Result, _STD move(_UResult.out)); + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + // clang-format on + private: + template + _NODISCARD static constexpr set_symmetric_difference_result<_It1, _It2, _Out> + _Set_symmetric_difference_unchecked(_It1 _First1, const _Se1 _Last1, _It2 _First2, const _Se2 _Last2, + _Out _Result, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2>); + + for (;;) { + if (_First1 == _Last1) { + auto _UResult = _RANGES _Copy_unchecked(_STD move(_First2), _STD move(_Last2), _STD move(_Result)); + return {_STD move(_First1), _STD move(_UResult.in), _STD move(_UResult.out)}; + } + + if (_First2 == _Last2) { + auto _UResult = _RANGES _Copy_unchecked(_STD move(_First1), _STD move(_Last1), _STD move(_Result)); + return {_STD move(_UResult.in), _STD move(_First2), _STD move(_UResult.out)}; + } + + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { + *_Result = *_First1; + ++_Result; + ++_First1; + } else if (_STD invoke(_Pred, _STD invoke(_Proj2, *_First2), _STD invoke(_Proj1, *_First1))) { + *_Result = *_First2; + ++_Result; + ++_First2; + } else { + ++_First1; + ++_First2; + } + } + } + }; + + inline constexpr _Set_symmetric_difference_fn set_symmetric_difference{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE max_element diff --git a/stl/inc/xutility b/stl/inc/xutility index 91b39c72cd9..ffced02cccc 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -1106,8 +1106,16 @@ concept permutable = forward_iterator<_It> && indirectly_movable_storable<_It, _It> && indirectly_swappable<_It, _It>; -// CONCEPT sortable +// CONCEPT mergeable namespace ranges { struct less; } +template +concept mergeable = input_iterator<_It1> && input_iterator<_It2> + && weakly_incrementable<_Out> + && indirectly_copyable<_It1, _Out> + && indirectly_copyable<_It2, _Out> + && indirect_strict_weak_order<_Pr, projected<_It1, _Pj1>, projected<_It2, _Pj2>>; + +// CONCEPT sortable template concept sortable = permutable<_It> && indirect_strict_weak_order<_Pr, projected<_It, _Proj>>; // clang-format on diff --git a/tests/std/test.lst b/tests/std/test.lst index 12dbf4d64a4..ab36cc7cee9 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -261,6 +261,7 @@ tests\P0896R4_ranges_alg_for_each_n tests\P0896R4_ranges_alg_generate tests\P0896R4_ranges_alg_generate_n tests\P0896R4_ranges_alg_heap +tests\P0896R4_ranges_alg_includes tests\P0896R4_ranges_alg_is_permutation tests\P0896R4_ranges_alg_is_sorted tests\P0896R4_ranges_alg_minmax @@ -282,6 +283,10 @@ tests\P0896R4_ranges_alg_reverse tests\P0896R4_ranges_alg_sample tests\P0896R4_ranges_alg_search tests\P0896R4_ranges_alg_search_n +tests\P0896R4_ranges_alg_set_difference +tests\P0896R4_ranges_alg_set_intersection +tests\P0896R4_ranges_alg_set_symmetric_difference +tests\P0896R4_ranges_alg_set_union tests\P0896R4_ranges_alg_shuffle tests\P0896R4_ranges_alg_swap_ranges tests\P0896R4_ranges_alg_transform_binary diff --git a/tests/std/tests/P0896R4_ranges_alg_includes/env.lst b/tests/std/tests/P0896R4_ranges_alg_includes/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_includes/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_includes/test.cpp b/tests/std/tests/P0896R4_ranges_alg_includes/test.cpp new file mode 100644 index 00000000000..082ca91f38e --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_includes/test.cpp @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +struct instantiator { + static constexpr P haystack_elements[] = {{0, 0}, {0, 1}, {1, 0}, {2, 0}, {2, 1}, {4, 0}, {4, 1}}; + static constexpr int needle_elements[] = {-1, 0, 3, 3}; + static constexpr int bad_needle_elements[] = {-1, 0, 2, 2}; + + static constexpr auto add_one = [](const int x) { return x + 1; }; + + template + static constexpr void call() { + using ranges::includes, ranges::less; + + { // Validate range overload + Haystack haystack{haystack_elements}; + Needle needle{needle_elements}; + const same_as auto result = includes(haystack, needle, ranges::less{}, get_first, add_one); + assert(result); + } + { // Validate iterator overload + Haystack haystack{haystack_elements}; + Needle needle{needle_elements}; + const same_as auto result = includes( + haystack.begin(), haystack.end(), needle.begin(), needle.end(), ranges::less{}, get_first, add_one); + assert(result); + } + + { // Validate range overload, empty haystack + Haystack haystack{}; + Needle needle{needle_elements}; + const same_as auto result = includes(haystack, needle, ranges::less{}, get_first, add_one); + assert(!result); + } + { // Validate iterator overload, empty needle + Haystack haystack{haystack_elements}; + Needle needle{}; + const same_as auto result = includes( + haystack.begin(), haystack.end(), needle.begin(), needle.end(), ranges::less{}, get_first, add_one); + assert(result); + } + + { // Validate range overload, needle not found + Haystack haystack{haystack_elements}; + Needle needle{bad_needle_elements}; + const same_as auto result = includes(haystack, needle, ranges::less{}, get_first, add_one); + assert(!result); + } + } +}; + +#ifdef TEST_EVERYTHING +int main() { + STATIC_ASSERT((test_in_in(), true)); + test_in_in(); +} +#else // ^^^ test all permutations of range properties / test only interesting permutations vvv +template +using test_range = test::range}, IsProxyRef>; + +constexpr void run_tests() { + using namespace test; + using test::iterator, test::range; + + // The algorithm is completely oblivious to: + // * categories stronger than input + // * whether the end sentinel is an iterator + // * size information + // * iterator and/or sentinel differencing + // so let's vary proxyness for coverage and add a range of each category out of paranoia. + + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); +} + +int main() { + STATIC_ASSERT((run_tests(), true)); + run_tests(); +} +#endif // TEST_EVERYTHING diff --git a/tests/std/tests/P0896R4_ranges_alg_set_difference/env.lst b/tests/std/tests/P0896R4_ranges_alg_set_difference/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_set_difference/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_set_difference/test.cpp b/tests/std/tests/P0896R4_ranges_alg_set_difference/test.cpp new file mode 100644 index 00000000000..6332bd4d468 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_set_difference/test.cpp @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +// Validate that set_difference_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, + ranges::less{}, identity{}, identity{})), + ranges::set_difference_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, + ranges::less{}, identity{}, identity{})), + ranges::set_difference_result>); + +struct instantiator { + static constexpr P elements1[] = {{0, 10}, {0, 11}, {0, 12}, {1, 10}, {1, 11}, {3, 10}}; + static constexpr P elements2[] = {{13, 0}, {14, 0}, {10, 2}, {11, 3}, {12, 3}}; + static constexpr P expected[] = {{0, 12}, {1, 10}, {1, 11}}; + + template + static constexpr void call() { + using ranges::set_difference, ranges::set_difference_result, ranges::equal, ranges::iterator_t, ranges::less; + + constexpr auto osize = ranges::size(elements1) + ranges::size(elements2); + + { // Validate range overload + P output[osize]{}; + R1 range1{elements1}; + R2 range2{elements2}; + const same_as, O>> auto result = + set_difference(range1, range2, O{output}, ranges::less{}, get_first, get_second); + assert(result.in == range1.end()); + assert(result.out.peek() == output + ranges::size(expected)); + assert(equal(span{output}.first(), expected)); + } + { // Validate iterator overload + P output[osize]{}; + R1 range1{elements1}; + R2 range2{elements2}; + const same_as, O>> auto result = set_difference(range1.begin(), + range1.end(), range2.begin(), range2.end(), O{output}, ranges::less{}, get_first, get_second); + assert(result.in == range1.end()); + assert(result.out.peek() == output + ranges::size(expected)); + assert(equal(span{output}.first(), expected)); + } + + { // Validate range overload, empty range1 + P output[osize]{}; + R1 range1{}; + R2 range2{elements2}; + const same_as, O>> auto result = + set_difference(range1, range2, O{output}, ranges::less{}, get_first, get_second); + assert(result.in == range1.end()); + assert(result.out.peek() == output); + } + { // Validate iterator overload, empty range2 + P output[osize]{}; + R1 range1{elements1}; + R2 range2{}; + const same_as, O>> auto result = set_difference(range1.begin(), + range1.end(), range2.begin(), range2.end(), O{output}, ranges::less{}, get_first, get_second); + assert(result.in == range1.end()); + assert(result.out.peek() == output + ranges::size(elements1)); + assert(equal(span{output}.first(), elements1)); + } + } +}; + +template +struct generate_readable_ranges { + template + static constexpr void call() { + using namespace test; + using test::range; + + // The algorithm is completely oblivious to: + // * categories stronger than input + // * whether the end sentinel is an iterator + // * size information + // * iterator and/or sentinel differencing + // so let's vary proxyness for coverage and call it good. + + Continuation::template call>(); + Continuation::template call>(); + } +}; + +template +struct generate_writable_iterators { + template + static constexpr void call() { + using namespace test; + using test::iterator; + + // The algorithm is completely oblivious to all properties except for proxyness, + // so again we'll vary that property, and we'll also get coverage from input iterators to ensure the algorithm + // doesn't inadvertently depend on the output_iterator-only `*i++ = meow` expression. + + Continuation::template call>(); + Continuation::template call>(); + + Continuation::template call>(); + Continuation::template call>(); + } +}; + +constexpr void run_tests() { + generate_readable_ranges>>::call(); +} + +int main() { + STATIC_ASSERT((run_tests(), true)); + run_tests(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_set_intersection/env.lst b/tests/std/tests/P0896R4_ranges_alg_set_intersection/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_set_intersection/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_set_intersection/test.cpp b/tests/std/tests/P0896R4_ranges_alg_set_intersection/test.cpp new file mode 100644 index 00000000000..ee621df8a6c --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_set_intersection/test.cpp @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +// Validate that set_intersection_result aliases in_in_out_result +STATIC_ASSERT( + same_as, ranges::in_in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, + ranges::less{}, identity{}, identity{})), + ranges::set_intersection_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, + ranges::less{}, identity{}, identity{})), + ranges::set_intersection_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, + ranges::less{}, identity{}, identity{})), + ranges::set_intersection_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, ranges::less{}, identity{}, identity{})), + ranges::set_intersection_result>); + +struct instantiator { + static constexpr P elements1[] = {{0, 10}, {0, 11}, {0, 12}, {1, 10}, {1, 11}, {3, 10}}; + static constexpr P elements2[] = {{13, 0}, {14, 0}, {10, 2}, {11, 3}, {12, 3}}; + static constexpr P expected[] = {{0, 10}, {0, 11}, {3, 10}}; + + template + static constexpr void call() { + using ranges::set_intersection, ranges::set_intersection_result, ranges::equal, ranges::iterator_t, + ranges::less; + + constexpr auto osize = ranges::size(elements1) + ranges::size(elements2); + + { // Validate range overload + P output[osize]{}; + R1 range1{elements1}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = + set_intersection(range1, range2, O{output}, ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(expected)); + assert(equal(span{output}.first(), expected)); + } + { // Validate iterator overload + P output[osize]{}; + R1 range1{elements1}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = + set_intersection(range1.begin(), range1.end(), range2.begin(), range2.end(), O{output}, ranges::less{}, + get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(expected)); + assert(equal(span{output}.first(), expected)); + } + + { // Validate range overload, empty range1 + P output[osize]{}; + R1 range1{}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = + set_intersection(range1, range2, O{output}, ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output); + } + { // Validate iterator overload, empty range2 + P output[osize]{}; + R1 range1{elements1}; + R2 range2{}; + const same_as, iterator_t, O>> auto result = + set_intersection(range1.begin(), range1.end(), range2.begin(), range2.end(), O{output}, ranges::less{}, + get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output); + } + } +}; + +template +struct generate_readable_ranges { + template + static constexpr void call() { + using namespace test; + using test::range; + + // The algorithm calls advance(i, s), so it's sensitive to CanDifference and Common in addition to the normal + // proxy sensitivity. + + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + Continuation::template call>(); + + Continuation::template call>(); + Continuation::template call>(); + } +}; + +template +struct generate_writable_iterators { + template + static constexpr void call() { + using namespace test; + using test::iterator; + + // The algorithm is completely oblivious to all properties except for proxyness, + // so again we'll vary that property, and we'll also get coverage from input iterators to ensure the algorithm + // doesn't inadvertently depend on the output_iterator-only `*i++ = meow` expression. + + Continuation::template call>(); + Continuation::template call>(); + + Continuation::template call>(); + Continuation::template call>(); + } +}; + +constexpr void run_tests() { + generate_readable_ranges>>::call(); +} + +int main() { + STATIC_ASSERT((run_tests(), true)); + run_tests(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_set_symmetric_difference/env.lst b/tests/std/tests/P0896R4_ranges_alg_set_symmetric_difference/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_set_symmetric_difference/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_set_symmetric_difference/test.cpp b/tests/std/tests/P0896R4_ranges_alg_set_symmetric_difference/test.cpp new file mode 100644 index 00000000000..d2e82ce92ea --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_set_symmetric_difference/test.cpp @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +// Validate that set_symmetric_difference_result aliases in_in_out_result +STATIC_ASSERT( + same_as, ranges::in_in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, + ranges::less{}, identity{}, identity{})), + ranges::set_symmetric_difference_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, + ranges::less{}, identity{}, identity{})), + ranges::set_symmetric_difference_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, + ranges::less{}, identity{}, identity{})), + ranges::set_symmetric_difference_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, ranges::less{}, identity{}, identity{})), + ranges::set_symmetric_difference_result>); + +struct instantiator { + static constexpr P elements1[] = {{0, 10}, {0, 11}, {0, 12}, {1, 10}, {1, 11}, {3, 10}}; + static constexpr P elements2[] = {{13, 0}, {14, 0}, {10, 2}, {11, 3}, {12, 3}}; + static constexpr P expected[] = {{0, 12}, {1, 10}, {1, 11}, {10, 2}, {12, 3}}; + + template + static constexpr void call() { + using ranges::set_symmetric_difference, ranges::set_symmetric_difference_result, ranges::equal, + ranges::iterator_t, ranges::less; + + constexpr auto osize = ranges::size(elements1) + ranges::size(elements2); + + { // Validate range overload + P output[osize]{}; + R1 range1{elements1}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = + set_symmetric_difference(range1, range2, O{output}, ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(expected)); + assert(equal(span{output}.first(), expected)); + } + { // Validate iterator overload + P output[osize]{}; + R1 range1{elements1}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = + set_symmetric_difference(range1.begin(), range1.end(), range2.begin(), range2.end(), O{output}, + ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(expected)); + assert(equal(span{output}.first(), expected)); + } + + { // Validate range overload, empty range1 + P output[osize]{}; + R1 range1{}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = + set_symmetric_difference(range1, range2, O{output}, ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(elements2)); + assert(equal(span{output}.first(), elements2)); + } + { // Validate iterator overload, empty range2 + P output[osize]{}; + R1 range1{elements1}; + R2 range2{}; + const same_as, iterator_t, O>> auto result = + set_symmetric_difference(range1.begin(), range1.end(), range2.begin(), range2.end(), O{output}, + ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(elements1)); + assert(equal(span{output}.first(), elements1)); + } + } +}; + +template +struct generate_readable_ranges { + template + static constexpr void call() { + using namespace test; + using test::range; + + // The algorithm is completely oblivious to: + // * categories stronger than input + // * whether the end sentinel is an iterator + // * size information + // * iterator and/or sentinel differencing + // so let's vary proxyness for coverage and call it good. + + Continuation::template call>(); + Continuation::template call>(); + } +}; + +template +struct generate_writable_iterators { + template + static constexpr void call() { + using namespace test; + using test::iterator; + + // The algorithm is completely oblivious to all properties except for proxyness, + // so again we'll vary that property, and we'll also get coverage from input iterators to ensure the algorithm + // doesn't inadvertently depend on the output_iterator-only `*i++ = meow` expression. + + Continuation::template call>(); + Continuation::template call>(); + + Continuation::template call>(); + Continuation::template call>(); + } +}; + +constexpr void run_tests() { + generate_readable_ranges>>::call(); +} + +int main() { + STATIC_ASSERT((run_tests(), true)); + run_tests(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_set_union/env.lst b/tests/std/tests/P0896R4_ranges_alg_set_union/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_set_union/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_set_union/test.cpp b/tests/std/tests/P0896R4_ranges_alg_set_union/test.cpp new file mode 100644 index 00000000000..3ab9116cfd9 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_set_union/test.cpp @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +// Validate that set_union_result aliases in_in_out_result +STATIC_ASSERT(same_as, ranges::in_in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, ranges::less{}, + identity{}, identity{})), + ranges::set_union_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, ranges::less{}, + identity{}, identity{})), + ranges::set_union_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, ranges::less{}, + identity{}, identity{})), + ranges::set_union_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, ranges::less{}, identity{}, identity{})), + ranges::set_union_result>); + +struct instantiator { + static constexpr P elements1[] = {{0, 10}, {0, 11}, {0, 12}, {1, 10}, {1, 11}, {3, 10}}; + static constexpr P elements2[] = {{13, 0}, {14, 0}, {10, 2}, {11, 3}, {12, 3}}; + static constexpr P expected[] = {{0, 10}, {0, 11}, {0, 12}, {1, 10}, {1, 11}, {10, 2}, {3, 10}, {12, 3}}; + + template + static constexpr void call() { + using ranges::set_union, ranges::set_union_result, ranges::equal, ranges::iterator_t, ranges::less; + + constexpr auto osize = ranges::size(elements1) + ranges::size(elements2); + + { // Validate range overload + P output[osize]{}; + R1 range1{elements1}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = + set_union(range1, range2, O{output}, ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(expected)); + assert(equal(span{output}.first(), expected)); + } + { // Validate iterator overload + P output[osize]{}; + R1 range1{elements1}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = set_union(range1.begin(), + range1.end(), range2.begin(), range2.end(), O{output}, ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(expected)); + assert(equal(span{output}.first(), expected)); + } + + { // Validate range overload, empty range1 + P output[osize]{}; + R1 range1{}; + R2 range2{elements2}; + const same_as, iterator_t, O>> auto result = + set_union(range1, range2, O{output}, ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(elements2)); + assert(equal(span{output}.first(), elements2)); + } + { // Validate iterator overload, empty range2 + P output[osize]{}; + R1 range1{elements1}; + R2 range2{}; + const same_as, iterator_t, O>> auto result = set_union(range1.begin(), + range1.end(), range2.begin(), range2.end(), O{output}, ranges::less{}, get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == output + ranges::size(elements1)); + assert(equal(span{output}.first(), elements1)); + } + } +}; + +template +struct generate_readable_ranges { + template + static constexpr void call() { + using namespace test; + using test::range; + + // The algorithm is completely oblivious to: + // * categories stronger than input + // * whether the end sentinel is an iterator + // * size information + // * iterator and/or sentinel differencing + // so let's vary proxyness for coverage and call it good. + + Continuation::template call>(); + Continuation::template call>(); + } +}; + +template +struct generate_writable_iterators { + template + static constexpr void call() { + using namespace test; + using test::iterator; + + // The algorithm is completely oblivious to all properties except for proxyness, + // so again we'll vary that property, and we'll also get coverage from input iterators to ensure the algorithm + // doesn't inadvertently depend on the output_iterator-only `*i++ = meow` expression. + + Continuation::template call>(); + Continuation::template call>(); + + Continuation::template call>(); + Continuation::template call>(); + } +}; + +constexpr void run_tests() { + generate_readable_ranges>>::call(); +} + +int main() { + STATIC_ASSERT((run_tests(), true)); + run_tests(); +} diff --git a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp index ff3beb645d5..ae315b2206c 100644 --- a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp @@ -767,6 +767,135 @@ namespace permutable_test { STATIC_ASSERT(permutable>); } // namespace permutable_test +namespace mergeable_test { + using ranges::less; + using std::identity, std::indirect_strict_weak_order, std::permutable, std::projected, std::mergeable; + + enum class readable_status { not_input_iter, good }; + + template + struct readable_archetype { + using value_type = T; + using difference_type = int; + + T const& operator*() const; + readable_archetype& operator++(); + + // clang-format off + // 0: not input_iterator + void operator++(int) requires (RS != readable_status::not_input_iter); + // clang-format on + }; + + enum class writable_status { not_weakly_incrementable, not_ind_copy_int, not_ind_copy_long, good }; + + template + struct writable_archetype { + using difference_type = int; + + writable_archetype& operator*(); + writable_archetype& operator++(); + + // clang-format off + writable_archetype operator++(int) requires (WS != writable_status::not_weakly_incrementable); + + // 1: not indirectly_copyable + void operator=(int) requires (WS == writable_status::not_ind_copy_int) = delete; + writable_archetype& operator=(int) requires (WS != writable_status::not_ind_copy_int); + + // 2: not indirectly_copyable + void operator=(long) requires (WS == writable_status::not_ind_copy_long) = delete; + writable_archetype& operator=(long) requires (WS != writable_status::not_ind_copy_long); + // clang-format on + }; + + void test() { + using std::indirect_strict_weak_order, std::indirectly_copyable, std::input_iterator, std::mergeable, + std::weakly_incrementable; + + using I1 = readable_archetype; + using I2 = readable_archetype; + using O = writable_archetype; + using Pr = ranges::less; + using Pj1 = std::identity; + using Pj2 = std::identity; + + { + using Bad_I1 = readable_archetype; + STATIC_ASSERT(!input_iterator); + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(weakly_incrementable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirect_strict_weak_order, projected>); + STATIC_ASSERT(!mergeable); + } + + { + using Bad_I2 = readable_archetype; + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(!input_iterator); + STATIC_ASSERT(weakly_incrementable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirect_strict_weak_order, projected>); + STATIC_ASSERT(!mergeable); + } + + { + using Bad_O = writable_archetype; + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(!weakly_incrementable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirect_strict_weak_order, projected>); + STATIC_ASSERT(!mergeable); + } + + { + using Bad_O = writable_archetype; + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(weakly_incrementable); + STATIC_ASSERT(!indirectly_copyable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirect_strict_weak_order, projected>); + STATIC_ASSERT(!mergeable); + } + + { + using Bad_O = writable_archetype; + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(weakly_incrementable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(!indirectly_copyable); + STATIC_ASSERT(indirect_strict_weak_order, projected>); + STATIC_ASSERT(!mergeable); + } + + { + using Bad_Pr = int; + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(weakly_incrementable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(!indirect_strict_weak_order, projected>); + STATIC_ASSERT(!mergeable); + } + + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(input_iterator); + STATIC_ASSERT(weakly_incrementable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirectly_copyable); + STATIC_ASSERT(indirect_strict_weak_order, projected>); + STATIC_ASSERT(mergeable); + } +} // namespace mergeable_test + namespace sortable_test { using ranges::less; using std::identity, std::indirect_strict_weak_order, std::permutable, std::projected, std::sortable; From 868cf267a0c1bdec6a21369f54c0541db3368abc Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 28 Jul 2020 15:31:55 -0700 Subject: [PATCH 19/50] Implement ranges::move_backward (#1053) * Implement ranges::move_backward Co-authored-by: Stephan T. Lavavej Co-authored-by: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> --- stl/inc/algorithm | 52 +++++++ tests/std/test.lst | 1 + .../P0896R4_ranges_alg_move_backward/env.lst | 4 + .../P0896R4_ranges_alg_move_backward/test.cpp | 141 ++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 tests/std/tests/P0896R4_ranges_alg_move_backward/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_move_backward/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index eb8559309ac..27099d60c70 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1888,6 +1888,58 @@ namespace ranges { }; inline constexpr _Move_fn move{_Not_quite_object::_Construct_tag{}}; + + // ALIAS TEMPLATE move_backward_result + template + using move_backward_result = in_out_result<_In, _Out>; + + // VARIABLE ranges::move_backward + // clang-format off + // concept-constrained for strict enforcement as it is used by several algorithms + template + requires indirectly_movable<_It1, _It2> + _NODISCARD constexpr _It2 _Move_backward_common(const _It1 _First, _It1 _Last, _It2 _Result) { + if constexpr (_Ptr_move_cat<_It1, _It2>::_Trivially_copyable) { + if (!_STD is_constant_evaluated()) { + return _Copy_backward_memmove(_First, _Last, _Result); + } + } + + while (_First != _Last) { + *--_Result = _RANGES iter_move(--_Last); + } + + return _Result; + } + // clang-format on + + class _Move_backward_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se1, bidirectional_iterator _It2> + requires indirectly_movable<_It1, _It2> + constexpr move_backward_result<_It1, _It2> operator()(_It1 _First, _Se1 _Last, _It2 _Result) const { + _Adl_verify_range(_First, _Last); + auto _UFirst = _Get_unwrapped(_STD move(_First)); + auto _ULast = _Get_final_iterator_unwrapped<_It1>(_UFirst, _STD move(_Last)); + _Seek_wrapped(_First, _ULast); + _Result = _RANGES _Move_backward_common(_STD move(_UFirst), _STD move(_ULast), _STD move(_Result)); + return {_STD move(_First), _STD move(_Result)}; + } + + template + requires indirectly_movable, _It> + constexpr move_backward_result, _It> operator()(_Rng&& _Range, _It _Result) const { + auto _ULast = _Get_final_iterator_unwrapped(_Range); + _Result = _RANGES _Move_backward_common(_Ubegin(_Range), _ULast, _STD move(_Result)); + return {_Rewrap_iterator(_Range, _STD move(_ULast)), _STD move(_Result)}; + } + // clang-format on + }; + + inline constexpr _Move_backward_fn move_backward{_Not_quite_object::_Construct_tag{}}; } // namespace ranges #endif // __cpp_lib_concepts diff --git a/tests/std/test.lst b/tests/std/test.lst index ab36cc7cee9..dc0c7187509 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -267,6 +267,7 @@ tests\P0896R4_ranges_alg_is_sorted tests\P0896R4_ranges_alg_minmax tests\P0896R4_ranges_alg_mismatch tests\P0896R4_ranges_alg_move +tests\P0896R4_ranges_alg_move_backward tests\P0896R4_ranges_alg_none_of tests\P0896R4_ranges_alg_partition tests\P0896R4_ranges_alg_partition_copy diff --git a/tests/std/tests/P0896R4_ranges_alg_move_backward/env.lst b/tests/std/tests/P0896R4_ranges_alg_move_backward/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_move_backward/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_move_backward/test.cpp b/tests/std/tests/P0896R4_ranges_alg_move_backward/test.cpp new file mode 100644 index 00000000000..06c17f7953f --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_move_backward/test.cpp @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +struct int_wrapper { + int val = 10; + + constexpr int_wrapper() = default; + constexpr int_wrapper(int x) : val{x} {} + constexpr int_wrapper(int_wrapper&& that) : val{exchange(that.val, -1)} {} + constexpr int_wrapper& operator=(int_wrapper&& that) { + val = exchange(that.val, -1); + return *this; + } + auto operator<=>(const int_wrapper&) const = default; +}; + +// Validate that move_backward_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, nullptr_to)), + ranges::move_backward_result>); +STATIC_ASSERT(same_as{}, nullptr_to)), + ranges::move_backward_result>); + +struct instantiator { + static constexpr int expected_output[] = {13, 42, 1729}; + static constexpr int expected_input[] = {-1, -1, -1}; + static constexpr int expected_overlapping[] = {-1, 0, 1, 2}; + + template + static constexpr void call() { +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 +#pragma warning(suppress : 4127) // conditional expression is constant + if (!ranges::contiguous_range || !is_constant_evaluated()) +#endif // TRANSITION, VSO-938163 + { + // For the second range, we need an iterator to the end; it's expedient to simply ignore ranges with + // differing iterator and sentinel types (i.e., ranges that don't satisfy common_range). + if constexpr (ranges::common_range) { + using ranges::move_backward, ranges::move_backward_result, ranges::equal, ranges::iterator_t; + + { // Validate range overload + int_wrapper input[] = {13, 42, 1729}; + int_wrapper output[] = {-2, -2, -2}; + R1 wrapped_input{input}; + R2 wrapped_output{output}; + same_as, iterator_t>> auto result = + move_backward(wrapped_input, wrapped_output.end()); + assert(result.in == wrapped_input.end()); + assert(result.out == wrapped_output.begin()); + assert(equal(output, expected_output, ranges::equal_to{}, &int_wrapper::val)); + assert(equal(input, expected_input, ranges::equal_to{}, &int_wrapper::val)); + } + { // Validate iterator + sentinel overload + int_wrapper input[] = {13, 42, 1729}; + int_wrapper output[] = {-2, -2, -2}; + R1 wrapped_input{input}; + R2 wrapped_output{output}; + same_as, iterator_t>> auto result = + move_backward(wrapped_input.begin(), wrapped_input.end(), wrapped_output.end()); + assert(result.in == wrapped_input.end()); + assert(result.out == wrapped_output.begin()); + assert(equal(output, expected_output, ranges::equal_to{}, &int_wrapper::val)); + assert(equal(input, expected_input, ranges::equal_to{}, &int_wrapper::val)); + } + { // Validate overlapping ranges + int_wrapper io[] = {0, 1, 2, 42}; + R1 wrapped_input{span{io}.first<3>()}; + R2 wrapped_output{span{io}.last<3>()}; + same_as, iterator_t>> auto result = + move_backward(wrapped_input, wrapped_output.end()); + assert(result.in == wrapped_input.end()); + assert(result.out == wrapped_output.begin()); + assert(equal(io, expected_overlapping, ranges::equal_to{}, &int_wrapper::val)); + } + } + } + } +}; + +constexpr void test_memmove() { + // Get some coverage for the memmove optimization, which we would not otherwise have since we do not currently + // unwrap output iterators. TRANSITION, GH-893 + using ranges::move_backward, ranges::move_backward_result, ranges::begin, ranges::end, ranges::equal; + + struct S { // move-only and trivially copyable + int val = 10; + + constexpr S() = default; + constexpr S(int x) : val{x} {} + constexpr S(S&&) = default; + constexpr S& operator=(S&&) = default; + auto operator<=>(const S&) const = default; + }; + + { // Validate range overload + S input[] = {13, 42, 1729}; + S output[] = {-2, -2, -2}; + const same_as> auto result = move_backward(input, end(output)); + assert(result.in == end(input)); + assert(result.out == begin(output)); + assert(equal(output, input)); + } + { // Validate iterator + sentinel overload + S input[] = {13, 42, 1729}; + S output[] = {-2, -2, -2}; + const same_as> auto result = move_backward(begin(input), end(input), end(output)); + assert(result.in == end(input)); + assert(result.out == begin(output)); + assert(equal(output, input)); + } + { // Validate overlapping ranges + S io[] = {0, 1, 2, 42}; + const same_as> auto result = move_backward(io + 0, io + 3, io + 4); + assert(result.in == io + 3); + assert(result.out == io + 1); + constexpr int expected[] = {0, 0, 1, 2}; + assert(equal(io, expected, ranges::equal_to{}, &S::val)); + } +} + +int main() { + STATIC_ASSERT((test_bidi_bidi(), true)); + test_bidi_bidi(); + + STATIC_ASSERT((test_memmove(), true)); + test_memmove(); +} From 35ce1cf44af6be5e6758ec79cfa61fa16995ce8b Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 28 Jul 2020 15:34:37 -0700 Subject: [PATCH 20/50] Fix the interaction between _Pass_fn and invoke (#1091) `_Pass_fn` returns a copy of arguments no larger than `void*`, and a call-forwarding reference-wrapper-alike `_Ref_fn` for larger function objects. This became a problem when Ranges started throwing pointers-to-member at `_Pass_fn` with the expectation that they would eventually be `invoke`d: pointers-to-member can be larger than `void*`, but `_Ref_fn` doesn't speak the `invoke` protocol. There are two relatively obvious possible fixes: 1. Teach `_Pass_fn` to always return copies of pointers-to-member regardless of size 2. Teach `_Ref_fn` to obey the `invoke` protocol (1) results in "large" pointers-to-member that can't be enregistered being copied and passed by hidden reference resulting in larger codesize than (2), but with no corresponding performance benefits. We therefore implement (2) by teaching `_Ref_fn` to call-forward through `invoke` when it wraps a pointer-to-member. Fixes GH-1089. --- stl/inc/xutility | 14 +++-- .../test.cpp | 54 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index ffced02cccc..1859f5f795e 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -190,16 +190,22 @@ template struct _Ref_fn { // pass function object by value as a reference template constexpr decltype(auto) operator()(_Args&&... _Vals) { // forward function call operator - return _Fn(_STD forward<_Args>(_Vals)...); +#if _HAS_IF_CONSTEXPR + if constexpr (is_member_pointer_v<_Fx>) { + return _STD invoke(_Fn, _STD forward<_Args>(_Vals)...); + } else +#endif // _HAS_IF_CONSTEXPR + { + return _Fn(_STD forward<_Args>(_Vals)...); + } } _Fx& _Fn; }; template -_INLINE_VAR constexpr bool - _Pass_functor_by_value_v = sizeof(_Fn) <= sizeof(void*) - && conjunction_v, is_trivially_destructible<_Fn>>; +_INLINE_VAR constexpr bool _Pass_functor_by_value_v = conjunction_v, + is_trivially_copy_constructible<_Fn>, is_trivially_destructible<_Fn>>; template , int> = 0> // TRANSITION, if constexpr constexpr _Fn _Pass_fn(_Fn _Val) { // pass functor by value diff --git a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp index ae315b2206c..ddb4888f0a3 100644 --- a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp @@ -929,3 +929,57 @@ namespace sortable_test { } } } // namespace sortable_test + +namespace gh_1089 { + // Defend against regression of GH-1089: "_Pass_fn/_Ref_fn interferes with the Ranges invoke protocol" + // The _Pass_fn protocol would previously assume that anything larger than a pointer was a function object that it + // could call with `()` and not a pointer-to-member that requires the `invoke` protocol. + + void test() { + { + struct Base { + virtual int purr() = 0; + }; + + struct Derived1 : virtual Base { + int purr() override { + return 1729; + } + }; + + struct Derived2 : virtual Base {}; + + struct MostDerived : Derived1, Derived2 { + int purr() override { + return 2020; + } + }; + + + STATIC_ASSERT(sizeof(&Derived1::purr) > sizeof(void*)); // NB: relies on non-portable platform properties + + Derived1 a[2]; + MostDerived b[3]; + Derived1* pointers[] = {&b[0], &a[0], &b[1], &a[1], &b[2]}; + + (void) ranges::count(pointers, 2020, &Derived1::purr); + } + { + struct Cat; + + using PMD_Cat = int Cat::*; + // Quantum effects: we must observe the size before defining Cat or it will become smaller. + STATIC_ASSERT(sizeof(PMD_Cat) > sizeof(void*)); + + struct Cat { + int x = 42; + }; + + STATIC_ASSERT(sizeof(&Cat::x) > sizeof(void*)); // NB: relies on non-portable platform properties + + Cat cats[42]; + + (void) ranges::count(cats, 42, &Cat::x); + } + } +} // namespace gh_1089 From 0e7b5d2509b96f7880bf69417264328df0d53c58 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Thu, 30 Jul 2020 06:30:29 +0300 Subject: [PATCH 21/50] : Implement copies for slice_array, gslice_array, mask_array, and indirect_array (#988) Co-authored-by: S. B. Tam Co-authored-by: Casey Carter Co-authored-by: Stephan T. Lavavej --- stl/inc/valarray | 78 +++++++++++++---- tests/libcxx/expected_results.txt | 4 - tests/libcxx/skipped_tests.txt | 4 - tests/std/test.lst | 1 + .../GH_000940_missing_valarray_copy/env.lst | 4 + .../GH_000940_missing_valarray_copy/test.cpp | 87 +++++++++++++++++++ 6 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 tests/std/tests/GH_000940_missing_valarray_copy/env.lst create mode 100644 tests/std/tests/GH_000940_missing_valarray_copy/test.cpp diff --git a/stl/inc/valarray b/stl/inc/valarray index d17080150c7..4c50bff4795 100644 --- a/stl/inc/valarray +++ b/stl/inc/valarray @@ -938,9 +938,16 @@ public: slice_array() = delete; - slice_array(const slice_array&); // not defined + slice_array(const slice_array&) = default; - slice_array& operator=(const slice_array&); // not defined + const slice_array& operator=(const slice_array& _Right) const { + size_t _Dst_off = _Start; + size_t _Src_off = _Right._Start; + for (size_t _Idx = 0; _Idx < _Len; ++_Idx, _Dst_off += _Stride, _Src_off += _Right._Stride) { + _Myptr[_Dst_off] = _Right._Myptr[_Src_off]; + } + return *this; + } private: friend valarray<_Ty>; @@ -992,7 +999,7 @@ public: return _Ans; } - size_t _Totlen() const { + _NODISCARD size_t _Totlen() const { if (_Len.size() == 0) { return 0; } @@ -1073,15 +1080,23 @@ public: _GSLOP(>>= _Right[_Idx]); } - _Ty& _Data(size_t _Idx) const { + _NODISCARD _Ty& _Data(size_t _Idx) const { return _Myptr[_Idx]; } gslice_array() = delete; - gslice_array(const gslice_array&); // not defined + gslice_array(const gslice_array&) = default; - gslice_array& operator=(const gslice_array&); // not defined + const gslice_array& operator=(const gslice_array& _Right) const { + _Sizarray _Dst_indexarray(size_t{0}, _Nslice()); + _Sizarray _Src_indexarray(size_t{0}, _Right._Nslice()); + const size_t _Size = _Totlen(); + for (size_t _Idx = 0; _Idx < _Size; ++_Idx) { + _Myptr[_Off(_Dst_indexarray)] = _Right._Myptr[_Right._Off(_Src_indexarray)]; + } + return *this; + } private: friend valarray<_Ty>; @@ -1155,15 +1170,32 @@ public: _MOP(>>= _Right[_Idx]); } - _Ty& _Data(size_t _Idx) const { + _NODISCARD _Ty& _Data(size_t _Idx) const { return _Myptr[_Idx]; } - bool _Mask(size_t _Idx) const { + _NODISCARD bool _Mask(size_t _Idx) const { return _Mybool[_Idx]; } - size_t _Totlen() const { + _NODISCARD size_t _Start_off() const { + size_t _Off = 0; + const size_t _Size = _Mybool.size(); + while (_Off < _Size && !_Mybool[_Off]) { + ++_Off; + } + return _Off; + } + + _NODISCARD size_t _Next_off(size_t _Off) const { + const size_t _Size = _Mybool.size(); + do { + ++_Off; + } while (_Off < _Size && !_Mybool[_Off]); + return _Off; + } + + _NODISCARD size_t _Totlen() const { size_t _Count = 0; for (size_t _Idx = 0; _Idx < _Mybool.size(); ++_Idx) { if (_Mybool[_Idx]) { @@ -1176,9 +1208,17 @@ public: mask_array() = delete; - mask_array(const mask_array&); // not defined + mask_array(const mask_array&) = default; - mask_array& operator=(const mask_array&); // not defined + const mask_array& operator=(const mask_array& _Right) const { + const size_t _Size = _Mybool.size(); + size_t _Dst_off = _Start_off(); + size_t _Src_off = _Right._Start_off(); + for (; _Dst_off < _Size; _Src_off = _Right._Next_off(_Src_off), _Dst_off = _Next_off(_Dst_off)) { + _Myptr[_Dst_off] = _Right._Myptr[_Src_off]; + } + return *this; + } private: friend valarray<_Ty>; @@ -1250,23 +1290,29 @@ public: _IOP(>>= _Right[_Idx]); } - _Ty& _Data(size_t _Idx) const { + _NODISCARD _Ty& _Data(size_t _Idx) const { return _Myptr[_Idx]; } - size_t _Indir(size_t _Idx) const { + _NODISCARD size_t _Indir(size_t _Idx) const { return _Myindarr[_Idx]; } - size_t _Totlen() const { + _NODISCARD size_t _Totlen() const { return _Myindarr.size(); } indirect_array() = delete; - indirect_array(const indirect_array&); // not defined + indirect_array(const indirect_array&) = default; - indirect_array& operator=(const indirect_array&); // not defined + const indirect_array& operator=(const indirect_array& _Right) const { + const size_t _Size = _Totlen(); + for (size_t _Idx = 0; _Idx < _Size; ++_Idx) { + _Myptr[_Indir(_Idx)] = _Right._Myptr[_Right._Indir(_Idx)]; + } + return *this; + } private: friend valarray<_Ty>; diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index b2753dc3709..8c91cb2d01e 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -565,10 +565,6 @@ std/re/re.traits/transform.pass.cpp FAIL # STL bug: Incorrect return types. std/numerics/complex.number/cmplx.over/pow.pass.cpp FAIL -# STL bug: Missing assignment operators. -std/numerics/numarray/template.mask.array/mask.array.assign/mask_array.pass.cpp FAIL -std/numerics/numarray/template.slice.array/slice.arr.assign/slice_array.pass.cpp FAIL - # STL bug: We allow fill() and swap() for array. std/containers/sequences/array/array.fill/fill.fail.cpp FAIL std/containers/sequences/array/array.swap/swap.fail.cpp FAIL diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 6f62c1c1b7d..61804e09fba 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -565,10 +565,6 @@ re\re.traits\transform.pass.cpp # STL bug: Incorrect return types. numerics\complex.number\cmplx.over\pow.pass.cpp -# STL bug: Missing assignment operators. -numerics\numarray\template.mask.array\mask.array.assign\mask_array.pass.cpp -numerics\numarray\template.slice.array\slice.arr.assign\slice_array.pass.cpp - # STL bug: We allow fill() and swap() for array. containers\sequences\array\array.fill\fill.fail.cpp containers\sequences\array\array.swap\swap.fail.cpp diff --git a/tests/std/test.lst b/tests/std/test.lst index dc0c7187509..9ce41e6b7c2 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -160,6 +160,7 @@ tests\GH_000545_include_compare tests\GH_000685_condition_variable_any tests\GH_000690_overaligned_function tests\GH_000890_pow_template +tests\GH_000940_missing_valarray_copy tests\GH_001010_filesystem_error_encoding tests\GH_001017_discrete_distribution_out_of_range tests\LWG2597_complex_branch_cut diff --git a/tests/std/tests/GH_000940_missing_valarray_copy/env.lst b/tests/std/tests/GH_000940_missing_valarray_copy/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_000940_missing_valarray_copy/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_000940_missing_valarray_copy/test.cpp b/tests/std/tests/GH_000940_missing_valarray_copy/test.cpp new file mode 100644 index 00000000000..91106130487 --- /dev/null +++ b/tests/std/tests/GH_000940_missing_valarray_copy/test.cpp @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +template +bool eq(const std::valarray& v, std::initializer_list il) { + return std::equal(begin(v), end(v), il.begin(), il.end()); +} + +void test_slice() { + std::valarray v{0, 1, 2, 3, 4}; + + std::slice_array slice_array = v[std::slice(2, 2, 2)]; + std::slice_array slice_array_copy = slice_array; + (void) slice_array_copy; + + assert(eq(v, {0, 1, 2, 3, 4})); + + std::slice_array other_slice_array = v[std::slice(0, 2, 1)]; + other_slice_array = slice_array; + + assert(eq(v, {2, 4, 2, 3, 4})); +} + +void test_gslice() { + std::valarray v{0, 1, 2, 3, 4}; + + std::gslice gslice(2, std::valarray{2}, std::valarray{2}); + std::gslice_array gslice_array = v[gslice]; + std::gslice_array gslice_array_copy = gslice_array; + (void) gslice_array_copy; + + assert(eq(v, {0, 1, 2, 3, 4})); + + std::gslice other_gslice(0, std::valarray{2}, std::valarray{1}); + std::gslice_array other_gslice_array = v[other_gslice]; + other_gslice_array = gslice_array; + + assert(eq(v, {2, 4, 2, 3, 4})); +} + +void test_mask() { + std::valarray v{0, 1, 2, 3, 4}; + + std::valarray mask{true, false, false, false, true}; + std::mask_array mask_array = v[mask]; + std::mask_array mask_array_copy = mask_array; + (void) mask_array_copy; + + assert(eq(v, {0, 1, 2, 3, 4})); + + std::valarray other_mask{false, true, true, false, false}; + std::mask_array other_mask_array = v[other_mask]; + other_mask_array = mask_array; + + assert(eq(v, {0, 0, 4, 3, 4})); +} + +void test_indirect() { + std::valarray v{0, 1, 2, 3, 4}; + + std::valarray indices{2, 3}; + std::indirect_array indirect_array = v[indices]; + std::indirect_array indirect_array_copy = indirect_array; + (void) indirect_array_copy; + + assert(eq(v, {0, 1, 2, 3, 4})); + + std::valarray other_indices{4, 0}; + std::indirect_array other_indirect_array = v[other_indices]; + other_indirect_array = indirect_array; + + assert(eq(v, {3, 1, 2, 3, 2})); +} + +int main() { + test_slice(); + test_gslice(); + test_mask(); + test_indirect(); + return 0; +} From e9f56a6148e969478c0203c10acf6919a83b2e53 Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 30 Jul 2020 11:49:20 +0800 Subject: [PATCH 22/50] Finish P0811R3 midpoint and lerp (#1048) * Removes workaround for missing `bit_cast` and mark `lerp` constexpr. * Changes how `lerp` handles infinite inputs according to https://github.com/microsoft/STL/issues/65#issuecomment-563811523 and https://github.com/microsoft/STL/issues/65#issuecomment-564102550. * Adds constexpr tests. Co-authored-by: Stephan T. Lavavej --- stl/inc/cmath | 108 ++-- stl/inc/numeric | 48 +- stl/inc/xutility | 96 +++- stl/inc/yvals_core.h | 2 +- tests/libcxx/expected_results.txt | 9 +- tests/libcxx/skipped_tests.txt | 9 +- tests/std/tests/P0811R3_midpoint_lerp/env.lst | 3 + .../std/tests/P0811R3_midpoint_lerp/test.cpp | 521 ++++++++++++++---- .../VSO_0157762_feature_test_macros/test.cpp | 14 + 9 files changed, 562 insertions(+), 248 deletions(-) diff --git a/stl/inc/cmath b/stl/inc/cmath index cf07a5b9424..5895847f4fd 100644 --- a/stl/inc/cmath +++ b/stl/inc/cmath @@ -12,6 +12,10 @@ #include #include +#if _HAS_CXX20 +#include +#endif // _HAS_CXX20 + #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) #pragma warning(disable : _STL_DISABLED_WARNINGS) @@ -1238,13 +1242,12 @@ _NODISCARD auto hypot(const _Ty1 _Dx, const _Ty2 _Dy, const _Ty3 _Dz) { #if _HAS_CXX20 // FUNCTION lerp -// TRANSITION, P0553: lerp is not yet constexpr template -_NODISCARD /* constexpr */ _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _Ty _ArgT) noexcept { +_NODISCARD constexpr _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _Ty _ArgT) noexcept { // on a line intersecting {(0.0, _ArgA), (1.0, _ArgB)}, return the Y value for X == _ArgT - const int _Finite_mask = (int{isfinite(_ArgA)} << 2) | (int{isfinite(_ArgB)} << 1) | int{isfinite(_ArgT)}; - if (_Finite_mask == 0b111) { + const bool _T_is_finite = _STD _Is_finite(_ArgT); + if (_T_is_finite && _STD _Is_finite(_ArgA) && _STD _Is_finite(_ArgB)) { // 99% case, put it first; this block comes from P0811R3 if ((_ArgA <= 0 && _ArgB >= 0) || (_ArgA >= 0 && _ArgB <= 0)) { // exact, monotonic, bounded, determinate, and (for _ArgA == _ArgB == 0) consistent: @@ -1272,96 +1275,61 @@ _NODISCARD /* constexpr */ _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, co return _Candidate; } - if (isnan(_ArgA)) { - return _ArgA; - } - - if (isnan(_ArgB)) { - return _ArgB; - } - - if (isnan(_ArgT)) { - return _ArgT; - } - - switch (_Finite_mask) { - case 0b000: - // All values are infinities - if (_ArgT >= 1) { - return _ArgB; - } - - return _ArgA; - case 0b010: - case 0b100: - case 0b110: - // _ArgT is an infinity; return infinity in the "direction" of _ArgA and _ArgB - return _ArgT * (_ArgB - _ArgA); - case 0b001: - // Here _ArgA and _ArgB are infinities - if (_ArgA == _ArgB) { - // same sign, so T doesn't matter - return _ArgA; - } - - // Opposite signs, choose the "infinity direction" according to T if it makes sense. - if (_ArgT <= 0) { + if (_STD is_constant_evaluated()) { + if (_STD _Is_nan(_ArgA)) { return _ArgA; } - if (_ArgT >= 1) { + if (_STD _Is_nan(_ArgB)) { return _ArgB; } - // Interpolating between infinities of opposite signs doesn't make sense, NaN - if constexpr (sizeof(_Ty) == sizeof(float)) { - return __builtin_nanf("0"); - } else { - return __builtin_nan("0"); - } - case 0b011: - // _ArgA is an infinity but _ArgB is not - if (_ArgT == 1) { - return _ArgB; + if (_STD _Is_nan(_ArgT)) { + return _ArgT; } - - if (_ArgT < 1) { - // towards the infinity, return it - return _ArgA; + } else { + // raise FE_INVALID if at least one of _ArgA, _ArgB, and _ArgT is signaling NaN + if (_STD _Is_nan(_ArgA) || _STD _Is_nan(_ArgB)) { + return (_ArgA + _ArgB) + _ArgT; } - // away from the infinity - return -_ArgA; - case 0b101: - // _ArgA is finite and _ArgB is an infinity - if (_ArgT == 0) { - return _ArgA; + if (_STD _Is_nan(_ArgT)) { + return _ArgT + _ArgT; } + } - if (_ArgT > 0) { - // toward the infinity - return _ArgB; + if (_T_is_finite) { + // _ArgT is finite, _ArgA and/or _ArgB is infinity + if (_ArgT < 0) { + // if _ArgT < 0: return infinity in the "direction" of _ArgA if that exists, NaN otherwise + return _ArgA - _ArgB; + } else if (_ArgT <= 1) { + // if _ArgT == 0: return _ArgA (infinity) if _ArgB is finite, NaN otherwise + // if 0 < _ArgT < 1: return infinity "between" _ArgA and _ArgB if that exists, NaN otherwise + // if _ArgT == 1: return _ArgB (infinity) if _ArgA is finite, NaN otherwise + return _ArgT * _ArgB + (1 - _ArgT) * _ArgA; + } else { + // if _ArgT > 1: return infinity in the "direction" of _ArgB if that exists, NaN otherwise + return _ArgB - _ArgA; } - - return -_ArgB; - case 0b111: // impossible; handled in fast path - default: - _CSTD abort(); + } else { + // _ArgT is an infinity; return infinity in the "direction" of _ArgA and _ArgB if that exists, NaN otherwise + return _ArgT * (_ArgB - _ArgA); } } // As of 2019-06-17 it is unclear whether the "sufficient additional overloads" clause is intended to target lerp; // LWG-3223 is pending. -_NODISCARD /* constexpr */ inline float lerp(const float _ArgA, const float _ArgB, const float _ArgT) noexcept { +_NODISCARD constexpr inline float lerp(const float _ArgA, const float _ArgB, const float _ArgT) noexcept { return _Common_lerp(_ArgA, _ArgB, _ArgT); } -_NODISCARD /* constexpr */ inline double lerp(const double _ArgA, const double _ArgB, const double _ArgT) noexcept { +_NODISCARD constexpr inline double lerp(const double _ArgA, const double _ArgB, const double _ArgT) noexcept { return _Common_lerp(_ArgA, _ArgB, _ArgT); } -_NODISCARD /* constexpr */ inline long double lerp( +_NODISCARD constexpr inline long double lerp( const long double _ArgA, const long double _ArgB, const long double _ArgT) noexcept { return _Common_lerp(_ArgA, _ArgB, _ArgT); } diff --git a/stl/inc/numeric b/stl/inc/numeric index 134385db5e4..687bc8a988a 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -536,21 +536,16 @@ _CONSTEXPR20 void iota(_FwdIt _First, _FwdIt _Last, _Ty _Val) { #if _HAS_CXX17 // FUNCTION TEMPLATE _Abs_u -template -_NODISCARD constexpr auto _Abs_u(const _Arithmetic _Val) noexcept { +template +_NODISCARD constexpr auto _Abs_u(const _Integral _Val) noexcept { // computes absolute value of _Val (converting to an unsigned integer type if necessary to avoid overflow // representing the negation of the minimum value) - if constexpr (is_floating_point_v<_Arithmetic>) { - // TRANSITION, P0553: this mishandles NaNs - if (_Val < 0) { - return -_Val; - } + static_assert(is_integral_v<_Integral>); - return _Val; - } else if constexpr (is_signed_v<_Arithmetic>) { - using _Unsigned = make_unsigned_t<_Arithmetic>; + if constexpr (is_signed_v<_Integral>) { + using _Unsigned = make_unsigned_t<_Integral>; if (_Val < 0) { - // note static_cast to _Unsigned such that _Arithmetic == short returns unsigned short rather than int + // note static_cast to _Unsigned such that _Integral == short returns unsigned short rather than int return static_cast<_Unsigned>(_Unsigned{0} - static_cast<_Unsigned>(_Val)); } @@ -619,9 +614,24 @@ _NODISCARD constexpr common_type_t<_Mt, _Nt> lcm(const _Mt _Mx, const _Nt _Nx) n template && !is_same_v, bool>, int> = 0> _NODISCARD constexpr _Ty midpoint(const _Ty _Val1, const _Ty _Val2) noexcept { if constexpr (is_floating_point_v<_Ty>) { + if (_STD is_constant_evaluated()) { + if (_STD _Is_nan(_Val1)) { + return _Val1; + } + + if (_STD _Is_nan(_Val2)) { + return _Val2; + } + } else { + if (_STD _Is_nan(_Val1) || _STD _Is_nan(_Val2)) { + // raise FE_INVALID if at least one of _Val1 and _Val2 is signaling NaN + return _Val1 + _Val2; + } + } + constexpr _Ty _High_limit = (numeric_limits<_Ty>::max)() / 2; - const auto _Val1_a = _Abs_u(_Val1); - const auto _Val2_a = _Abs_u(_Val2); + const auto _Val1_a = _Float_abs(_Val1); + const auto _Val2_a = _Float_abs(_Val2); if (_Val1_a <= _High_limit && _Val2_a <= _High_limit) { // _Val1 and _Val2 are small enough that _Val1 + _Val2 won't overflow @@ -637,22 +647,12 @@ _NODISCARD constexpr _Ty midpoint(const _Ty _Val1, const _Ty _Val2) noexcept { return (_Val1 + _Val2) / 2; } - // TRANSITION, P0553: the next two branches handle NaNs but don't produce correct behavior under /fp:fast or - // -fassociative-math - if (_Val1 != _Val1) { - return _Val1; - } - - if (_Val2 != _Val2) { - return _Val2; - } - // Here at least one of {_Val1, _Val2} has large magnitude. // Therefore, if one of the values is too small to divide by 2 exactly, the small magnitude is much less than // one ULP of the result, so we can add it directly without the potentially inexact division by 2. // In the default rounding mode this less than one ULP difference will always be rounded away, so under - // /fp:precise or /fp:fast we could avoid these tests if we had some means of detecting it in the caller. + // /fp:fast we could avoid these tests if we had some means of detecting it in the caller. constexpr _Ty _Low_limit = (numeric_limits<_Ty>::min)() * 2; if (_Val1_a < _Low_limit) { return _Val1 + _Val2 / 2; diff --git a/stl/inc/xutility b/stl/inc/xutility index 1859f5f795e..fd38d518b3c 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -27,6 +27,12 @@ _STL_DISABLE_CLANG_WARNINGS #define _USE_STD_VECTOR_ALGORITHMS 0 #endif +#ifdef __CUDACC__ +#define _CONSTEXPR_BIT_CAST inline +#else // ^^^ workaround ^^^ / vvv no workaround vvv +#define _CONSTEXPR_BIT_CAST constexpr +#endif // ^^^ no workaround ^^^ + #if _USE_STD_VECTOR_ALGORITHMS _EXTERN_C // The "noalias" attribute tells the compiler optimizer that pointers going into these hand-vectorized algorithms @@ -52,17 +58,15 @@ template , is_trivially_copyable<_To>, is_trivially_copyable<_From>>, int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST _To _Bit_cast(const _From& _Val) noexcept { #ifdef __CUDACC__ -_NODISCARD _To _Bit_cast(const _From& _Val) noexcept { _To _To_obj; // assumes default-init _CSTD memcpy(_STD addressof(_To_obj), _STD addressof(_Val), sizeof(_To)); return _To_obj; -} #else // ^^^ workaround ^^^ / vvv no workaround vvv -_NODISCARD constexpr _To _Bit_cast(const _From& _Val) noexcept { return __builtin_bit_cast(_To, _Val); -} #endif // ^^^ no workaround ^^^ +} // STRUCT TEMPLATE _Get_first_parameter template @@ -1149,19 +1153,19 @@ struct _Iterator_traits_base<_Iter, typename _Iter::pointer, typename _Iter::reference>> { // defined if _Iter::* types exist using iterator_category = typename _Iter::iterator_category; - using value_type = typename _Iter::value_type; - using difference_type = typename _Iter::difference_type; - using pointer = typename _Iter::pointer; - using reference = typename _Iter::reference; + using value_type = typename _Iter::value_type; + using difference_type = typename _Iter::difference_type; + using pointer = typename _Iter::pointer; + using reference = typename _Iter::reference; }; template > struct _Iterator_traits_pointer_base { // iterator properties for pointers to object using iterator_category = random_access_iterator_tag; - using value_type = remove_cv_t<_Ty>; - using difference_type = ptrdiff_t; - using pointer = _Ty*; - using reference = _Ty&; + using value_type = remove_cv_t<_Ty>; + using difference_type = ptrdiff_t; + using pointer = _Ty*; + using reference = _Ty&; }; template @@ -1421,8 +1425,8 @@ _NODISCARD constexpr decltype(auto) _Get_unwrapped_n(_Iter&& _It, const _Diff _O template && is_integral_v<_Diff>, int> = 0> _NODISCARD constexpr decltype(auto) _Get_unwrapped_n(_Iter&& _It, const _Diff _Off) { // ask an iterator to assert that the iterator moved _Off positions is valid, and unwrap - using _IDiff = _Iter_diff_t<_Remove_cvref_t<_Iter>>; - using _CDiff = common_type_t<_Diff, _IDiff>; + using _IDiff = _Iter_diff_t<_Remove_cvref_t<_Iter>>; + using _CDiff = common_type_t<_Diff, _IDiff>; const auto _COff = static_cast<_CDiff>(_Off); _STL_ASSERT(_COff <= static_cast<_CDiff>(_Max_possible_v<_IDiff>) @@ -1731,7 +1735,7 @@ _CONSTEXPR17 void _Advance1(_InIt& _Where, _Diff _Off, input_iterator_tag) { // increment iterator by offset, input iterators _STL_ASSERT(_Off >= 0, "negative advance of non-bidirectional iterator"); - decltype(auto) _UWhere = _Get_unwrapped_n(_STD move(_Where), _Off); + decltype(auto) _UWhere = _Get_unwrapped_n(_STD move(_Where), _Off); constexpr bool _Need_rewrap = !is_reference_v; for (; 0 < _Off; --_Off) { @@ -1746,7 +1750,7 @@ _CONSTEXPR17 void _Advance1(_InIt& _Where, _Diff _Off, input_iterator_tag) { template _CONSTEXPR17 void _Advance1(_BidIt& _Where, _Diff _Off, bidirectional_iterator_tag) { // increment iterator by offset, bidirectional iterators - decltype(auto) _UWhere = _Get_unwrapped_n(_STD move(_Where), _Off); + decltype(auto) _UWhere = _Get_unwrapped_n(_STD move(_Where), _Off); constexpr bool _Need_rewrap = !is_reference_v; for (; 0 < _Off; --_Off) { @@ -1799,8 +1803,8 @@ template _CONSTEXPR17 _Iter_diff_t<_InIt> _Distance1(_InIt _First, _InIt _Last, input_iterator_tag) { // return distance between iterators; input _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_First); - const auto _ULast = _Get_unwrapped(_Last); + auto _UFirst = _Get_unwrapped(_First); + const auto _ULast = _Get_unwrapped(_Last); _Iter_diff_t<_InIt> _Off = 0; for (; _UFirst != _ULast; ++_UFirst) { ++_Off; @@ -4431,7 +4435,7 @@ _OutIt copy_n(_InIt _First, _Diff _Count_raw, _OutIt _Dest) { // copy [_First, _ const _Algorithm_int_t<_Diff> _Count = _Count_raw; if (0 < _Count) { auto _UFirst = _Get_unwrapped_n(_First, _Count); - auto _UDest = _Get_unwrapped_n(_Dest, _Count); + auto _UDest = _Get_unwrapped_n(_Dest, _Count); _Seek_wrapped( _Dest, _Copy_n_unchecked4(_UFirst, _Count, _UDest, bool_constant<_Ptr_copy_cat::_Trivially_copyable>{})); @@ -4518,9 +4522,9 @@ _BidIt2 _Copy_backward_unchecked(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest, t template _BidIt2 copy_backward(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest) { // copy [_First, _Last) backwards to [..., _Dest) _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_First); + auto _UFirst = _Get_unwrapped(_First); const auto _ULast = _Get_unwrapped(_Last); - auto _UDest = _Get_unwrapped_n(_Dest, -_Idl_distance<_BidIt1>(_UFirst, _ULast)); + auto _UDest = _Get_unwrapped_n(_Dest, -_Idl_distance<_BidIt1>(_UFirst, _ULast)); _Seek_wrapped(_Dest, _Copy_backward_unchecked(_UFirst, _ULast, _UDest, bool_constant<_Ptr_copy_cat::_Trivially_copyable>{})); return _Dest; @@ -4969,7 +4973,7 @@ bool _Equal_unchecked(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _F // compare [_First1, _Last1) to [_First2, ...), memcmp optimization const auto _First1_ch = reinterpret_cast(_First1); const auto _First2_ch = reinterpret_cast(_First2); - const auto _Count = static_cast(reinterpret_cast(_Last1) - _First1_ch); + const auto _Count = static_cast(reinterpret_cast(_Last1) - _First1_ch); return _CSTD memcmp(_First1_ch, _First2_ch, _Count) == 0; } @@ -4978,7 +4982,7 @@ _NODISCARD bool equal(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _F // compare [_First1, _Last1) to [_First2, ...) _Adl_verify_range(_First1, _Last1); const auto _UFirst1 = _Get_unwrapped(_First1); - const auto _ULast1 = _Get_unwrapped(_Last1); + const auto _ULast1 = _Get_unwrapped(_Last1); const auto _UFirst2 = _Get_unwrapped_n(_First2, _Idl_distance<_InIt1>(_UFirst1, _ULast1)); return _Equal_unchecked(_UFirst1, _ULast1, _UFirst2, _Pass_fn(_Pred)); } @@ -5867,7 +5871,53 @@ struct _CXX17_DEPRECATE_ITERATOR_BASE_CLASS iterator { // base type for iterator using pointer = _Pointer; using reference = _Reference; }; + +// STRUCT TEMPLATE _Float_traits +template +struct _Float_traits { + static_assert(is_floating_point_v<_Ty>, "_Float_traits is invalid"); + + // traits for double and long double: + using type = unsigned long long; + + static constexpr type _Magnitude_mask = 0x7fff'ffff'ffff'ffffULL; + static constexpr type _Exponent_mask = 0x7ff0'0000'0000'0000ULL; +}; + +template <> +struct _Float_traits { + using type = unsigned int; + + static constexpr type _Magnitude_mask = 0x7fff'ffffU; + static constexpr type _Exponent_mask = 0x7f80'0000U; +}; + +// FUNCTION TEMPLATE _Float_abs_bits +template , int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST auto _Float_abs_bits(const _Ty& _Xx) { + const auto _Bits = _Bit_cast::type>(_Xx); + return _Bits & _Float_traits<_Ty>::_Magnitude_mask; +} + +// FUNCTION TEMPLATE _Float_abs +template , int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST _Ty _Float_abs(const _Ty _Xx) { // constexpr floating point abs() + return _Bit_cast<_Ty>(_Float_abs_bits(_Xx)); +} + +// FUNCTION TEMPLATE _Is_nan +template , int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST bool _Is_nan(const _Ty _Xx) { // constexpr isnan() + return _Float_abs_bits(_Xx) > _Float_traits<_Ty>::_Exponent_mask; +} + +// FUNCTION TEMPLATE _Is_finite +template , int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST bool _Is_finite(const _Ty _Xx) { // constexpr isfinite() + return _Float_abs_bits(_Xx) < _Float_traits<_Ty>::_Exponent_mask; +} _STD_END +#undef _CONSTEXPR_BIT_CAST #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 61292ad030b..41fa0568e92 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -164,7 +164,6 @@ // (partially implemented) // P0769R2 shift_left(), shift_right() // P0811R3 midpoint(), lerp() -// (partially implemented, lerp() not yet constexpr) // P0879R0 constexpr For Swapping Functions // P0887R1 type_identity // P0896R4 Ranges @@ -1169,6 +1168,7 @@ #define __cpp_lib_generic_unordered_lookup 201811L #define __cpp_lib_int_pow2 202002L #define __cpp_lib_integer_comparison_functions 202002L +#define __cpp_lib_interpolate 201902L #define __cpp_lib_is_constant_evaluated 201811L #define __cpp_lib_is_nothrow_convertible 201806L #define __cpp_lib_list_remove_return_type 201806L diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 8c91cb2d01e..88a11df330a 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -465,10 +465,6 @@ std/utilities/variant/variant.variant/variant.ctor/T.pass.cpp FAIL # C++20 P0768R1 "Library Support for the Spaceship (Comparison) Operator" std/language.support/support.limits/support.limits.general/compare.version.pass.cpp FAIL -# C++20 P0811R2 "midpoint(), lerp()" -std/language.support/support.limits/support.limits.general/numeric.version.pass.cpp FAIL -std/numerics/c.math/c.math.lerp/c.math.lerp.pass.cpp FAIL - # C++20 P0896R4 "" std/language.support/support.limits/support.limits.general/algorithm.version.pass.cpp FAIL std/language.support/support.limits/support.limits.general/functional.version.pass.cpp FAIL @@ -775,6 +771,11 @@ std/iterators/predef.iterators/insert.iterators/insert.iterator/types.pass.cpp F std/numerics/complex.number/cmplx.over/conj.pass.cpp:0 FAIL std/numerics/complex.number/cmplx.over/proj.pass.cpp:0 FAIL +# Assertion failed: (std::lerp(T(2.3), T(2.3), inf) == T(2.3)) +# Asserts `(std::lerp(T(2.3), T(2.3), inf) == T(2.3))` and `std::isnan(std::lerp(T( 0), T( 0), inf))` +# They shouldn't behave differently. Both of them should probably return NaN. +std/numerics/c.math/c.math.lerp/c.math.lerp.pass.cpp FAIL + # *** LIKELY STL BUGS *** # Not yet analyzed, likely STL bugs. Assertions and other runtime failures. diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 61804e09fba..133b8b06e8b 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -465,10 +465,6 @@ utilities\variant\variant.variant\variant.ctor\T.pass.cpp # C++20 P0768R1 "Library Support for the Spaceship (Comparison) Operator" language.support\support.limits\support.limits.general\compare.version.pass.cpp -# C++20 P0811R2 "midpoint(), lerp()" -language.support\support.limits\support.limits.general\numeric.version.pass.cpp -numerics\c.math\c.math.lerp\c.math.lerp.pass.cpp - # C++20 P0896R4 "" language.support\support.limits\support.limits.general\algorithm.version.pass.cpp language.support\support.limits\support.limits.general\functional.version.pass.cpp @@ -775,6 +771,11 @@ iterators\predef.iterators\insert.iterators\insert.iterator\types.pass.cpp numerics\complex.number\cmplx.over\conj.pass.cpp numerics\complex.number\cmplx.over\proj.pass.cpp +# Assertion failed: (std::lerp(T(2.3), T(2.3), inf) == T(2.3)) +# Asserts `(std::lerp(T(2.3), T(2.3), inf) == T(2.3))` and `std::isnan(std::lerp(T( 0), T( 0), inf))` +# They shouldn't behave differently. Both of them should probably return NaN. +numerics\c.math\c.math.lerp\c.math.lerp.pass.cpp + # *** LIKELY STL BUGS *** # Not yet analyzed, likely STL bugs. Assertions and other runtime failures. diff --git a/tests/std/tests/P0811R3_midpoint_lerp/env.lst b/tests/std/tests/P0811R3_midpoint_lerp/env.lst index 642f530ffad..6d173ad8f69 100644 --- a/tests/std/tests/P0811R3_midpoint_lerp/env.lst +++ b/tests/std/tests/P0811R3_midpoint_lerp/env.lst @@ -2,3 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception RUNALL_INCLUDE ..\usual_latest_matrix.lst +RUNALL_CROSSLIST +PM_CL="/Od" +PM_CL="/O2" diff --git a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp index 211dd273a74..af6dedda278 100644 --- a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp +++ b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp @@ -1,12 +1,16 @@ // 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 @@ -19,13 +23,16 @@ using namespace std; template using limits = numeric_limits; +// "major" floating point exceptions, excluding underflow and inexact +constexpr int fe_major_except = FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW; + #ifdef _M_FP_STRICT // According to: // https://docs.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior // Under the default /fp:precise mode: // The compiler generates code intended to run in the default floating-point environment and assumes that the // floating-point environment is not accessed or modified at runtime. -// ... so we only do testing of rounding modes when strict is enabled. +// ... so we only do testing of rounding modes and floating-point exceptions when strict is enabled. // TRANSITION, VSO-923474 -- should be #pragma STDC FENV_ACCESS ON #pragma fenv_access(on) @@ -51,49 +58,89 @@ class RoundGuard { private: int oldRound; }; -#endif // _M_FP_STRICT + +void checked_feholdexcept(fenv_t* const env) { + [[maybe_unused]] const int holdExcept = feholdexcept(env); + assert(holdExcept == 0); +} + +void checked_fesetenv(const fenv_t* const env) { + [[maybe_unused]] const int setEnv = fesetenv(env); + assert(setEnv == 0); +} + +class ExceptGuard { +public: + ExceptGuard() { + checked_feholdexcept(&env); + } + + ExceptGuard(const ExceptGuard&) = delete; + ExceptGuard& operator=(const ExceptGuard&) = delete; + + ~ExceptGuard() { + checked_fesetenv(&env); + } + +private: + fenv_t env; +}; + +bool check_feexcept(const int expected_excepts, const int except_mask = fe_major_except) { + return fetestexcept(except_mask) == (expected_excepts & except_mask); +} +#else // ^^^ defined(_M_FP_STRICT) / !defined(_M_FP_STRICT) vvv +class ExceptGuard { +public: + ExceptGuard() {} + + ExceptGuard(const ExceptGuard&) = delete; + ExceptGuard& operator=(const ExceptGuard&) = delete; + + ~ExceptGuard() {} +}; + +bool check_feexcept( + [[maybe_unused]] const int expected_excepts, [[maybe_unused]] const int except_mask = fe_major_except) { + return true; +} +#endif // ^^^ !defined(_M_FP_STRICT) ^^^ template -Ty mint_nan(const bool sign, const unsigned long long payload); +constexpr Ty mint_nan(const bool sign, const unsigned long long payload); template <> -float mint_nan(const bool sign, const unsigned long long payload) { - const unsigned int filteredPayload = payload & 0x7F'FFFFu; // bottom 23 bits +constexpr float mint_nan(const bool sign, const unsigned long long payload) { + const unsigned int filteredPayload = payload & 0x3F'FFFFu; // bottom 22 bits assert(filteredPayload == payload); // if this assert fails, payload didn't fit - assert(filteredPayload != 0); // if this assert fails, the NaN would be an infinity // clang-format off const unsigned int result = (static_cast(sign) << 31) - | 0x7F80'0000u // turn on all exponent bits + | 0x7FC0'0000u // turn on all exponent bits and the qNaN bit | filteredPayload; // clang-format on - float resultConverted; // TRANSITION, bit_cast - memcpy(&resultConverted, &result, sizeof(result)); - return resultConverted; + return bit_cast(result); } template <> -double mint_nan(const bool sign, const unsigned long long payload) { - const unsigned long long filteredPayload = payload & 0xF'FFFF'FFFF'FFFFllu; // bottom 52 bits +constexpr double mint_nan(const bool sign, const unsigned long long payload) { + const unsigned long long filteredPayload = payload & 0x7'FFFF'FFFF'FFFFllu; // bottom 51 bits assert(filteredPayload == payload); // if this assert fails, payload didn't fit - assert(filteredPayload != 0); // if this assert fails, the NaN would be an infinity // clang-format off const unsigned long long result = (static_cast(sign) << 63) - | 0x7FF0'0000'0000'0000u // turn on all exponent bits + | 0x7FF8'0000'0000'0000u // turn on all exponent bits and the qNaN bit | filteredPayload; // clang-format on - double resultConverted; // TRANSITION, bit_cast - memcpy(&resultConverted, &result, sizeof(result)); - return resultConverted; + return bit_cast(result); } template <> -long double mint_nan(const bool sign, const unsigned long long payload) { +constexpr long double mint_nan(const bool sign, const unsigned long long payload) { return mint_nan(sign, payload); } @@ -102,17 +149,36 @@ void assert_bitwise_equal(const Ty& a, const Ty& b) { assert(memcmp(&a, &b, sizeof(Ty)) == 0); } +// TRANSITION +// numeric_limits::signaling_NaN() doesn't work on x86 hosted MSVC +// numeric_limits::signaling_NaN() doesn't work on x64 hosted MSVC +void make_snan(float& x) { + constexpr unsigned int bits = 0x7f80'0001U; + memcpy(&x, &bits, sizeof(x)); +} + +void make_snan(double& x) { + constexpr unsigned long long bits = 0x7ff0'0000'0000'0001ULL; + memcpy(&x, &bits, sizeof(x)); +} + +void make_snan(long double& x) { + constexpr unsigned long long bits = 0x7ff0'0000'0000'0001ULL; + memcpy(&x, &bits, sizeof(x)); +} + template struct constants; // not defined template <> struct constants { - static constexpr float TwoPlusUlp = 0x1.000002p+1f; - static constexpr float OnePlusUlp = 0x1.000002p+0f; - static constexpr float PointFivePlusUlp = 0x1.000002p-1f; - static constexpr float OneMinusUlp = 0x1.fffffep-1f; - static constexpr float NegOneMinusUlp = -OnePlusUlp; - static constexpr float NegOnePlusUlp = -OneMinusUlp; + static constexpr float TwoPlusUlp = 0x1.000002p+1f; + static constexpr float OnePlusUlp = 0x1.000002p+0f; + static constexpr float PointFivePlusUlp = 0x1.000002p-1f; + static constexpr float OneMinusUlp = 0x1.fffffep-1f; + static constexpr float PointFiveMinusUlp = 0x1.fffffep-2f; + static constexpr float NegOneMinusUlp = -OnePlusUlp; + static constexpr float NegOnePlusUlp = -OneMinusUlp; static constexpr float EighthPlusUlp = 0x1.000002p-3f; static constexpr float EighthMinusUlp = 0x1.fffffep-4f; @@ -120,12 +186,13 @@ struct constants { template <> struct constants { - static constexpr double TwoPlusUlp = 0x1.0000000000001p+1; - static constexpr double OnePlusUlp = 0x1.0000000000001p+0; - static constexpr double PointFivePlusUlp = 0x1.0000000000001p-1; - static constexpr double OneMinusUlp = 0x1.fffffffffffffp-1; - static constexpr double NegOneMinusUlp = -OnePlusUlp; - static constexpr double NegOnePlusUlp = -OneMinusUlp; + static constexpr double TwoPlusUlp = 0x1.0000000000001p+1; + static constexpr double OnePlusUlp = 0x1.0000000000001p+0; + static constexpr double PointFivePlusUlp = 0x1.0000000000001p-1; + static constexpr double OneMinusUlp = 0x1.fffffffffffffp-1; + static constexpr double PointFiveMinusUlp = 0x1.fffffffffffffp-2; + static constexpr double NegOneMinusUlp = -OnePlusUlp; + static constexpr double NegOnePlusUlp = -OneMinusUlp; static constexpr double EighthPlusUlp = 0x1.0000000000001p-3; static constexpr double EighthMinusUlp = 0x1.fffffffffffffp-4; @@ -133,12 +200,13 @@ struct constants { template <> struct constants { - static constexpr long double TwoPlusUlp = 0x1.0000000000001p+1; - static constexpr long double OnePlusUlp = 0x1.0000000000001p+0; - static constexpr long double PointFivePlusUlp = 0x1.0000000000001p-1; - static constexpr long double OneMinusUlp = 0x1.fffffffffffffp-1; - static constexpr long double NegOneMinusUlp = -OnePlusUlp; - static constexpr long double NegOnePlusUlp = -OneMinusUlp; + static constexpr long double TwoPlusUlp = 0x1.0000000000001p+1; + static constexpr long double OnePlusUlp = 0x1.0000000000001p+0; + static constexpr long double PointFivePlusUlp = 0x1.0000000000001p-1; + static constexpr long double OneMinusUlp = 0x1.fffffffffffffp-1; + static constexpr long double PointFiveMinusUlp = 0x1.fffffffffffffp-2; + static constexpr long double NegOneMinusUlp = -OnePlusUlp; + static constexpr long double NegOnePlusUlp = -OneMinusUlp; static constexpr long double EighthPlusUlp = 0x1.0000000000001p-3; static constexpr long double EighthMinusUlp = 0x1.fffffffffffffp-4; @@ -150,6 +218,7 @@ void test_constants() { assert(constants::OnePlusUlp == nextafter(Ty(1.0), Ty(3.0))); assert(constants::PointFivePlusUlp == nextafter(Ty(0.5), Ty(3.0))); assert(constants::OneMinusUlp == nextafter(Ty(1.0), Ty(0.0))); + assert(constants::PointFiveMinusUlp == nextafter(Ty(0.5), Ty(-2.0))); assert(constants::NegOneMinusUlp == nextafter(Ty(-1.0), Ty(-2.0))); assert(constants::NegOnePlusUlp == nextafter(Ty(-1.0), Ty(0.0))); @@ -326,6 +395,7 @@ void test_midpoint_floating() { #ifdef _M_FP_STRICT { // test results exactly between 1 ULP: + ExceptGuard except; RoundGuard round{FE_UPWARD}; assert(midpoint(Ty(1.0), constants::OnePlusUlp) == constants::OnePlusUlp); assert(midpoint(Ty(1.0), constants::OneMinusUlp) == Ty(1.0)); @@ -356,10 +426,13 @@ void test_midpoint_floating() { assert(midpoint(-limits::min(), -limits::max()) == -limits::max() / 2); assert(midpoint(-limits::denorm_min(), limits::max()) == limits::max() / 2); assert(midpoint(-limits::denorm_min(), -limits::max()) == -limits::max() / 2); + + assert(check_feexcept(0)); } // ditto for the other rounding modes: { + ExceptGuard except; RoundGuard round{FE_DOWNWARD}; assert(midpoint(Ty(1.0), constants::OnePlusUlp) == Ty(1.0)); assert(midpoint(Ty(1.0), constants::OneMinusUlp) == constants::OneMinusUlp); @@ -394,9 +467,12 @@ void test_midpoint_floating() { == nextafter(limits::max() / 2, limits::lowest())); assert(midpoint(-limits::denorm_min(), -limits::max()) == nextafter(-limits::max() / 2, limits::lowest())); + + assert(check_feexcept(0)); } { + ExceptGuard except; RoundGuard round{FE_TOWARDZERO}; assert(midpoint(Ty(1.0), constants::OnePlusUlp) == Ty(1.0)); assert(midpoint(Ty(1.0), constants::OneMinusUlp) == constants::OneMinusUlp); @@ -422,27 +498,49 @@ void test_midpoint_floating() { assert(midpoint(-limits::min(), -limits::max()) == -limits::max() / 2); assert(midpoint(-limits::denorm_min(), limits::max()) == nextafter(limits::max() / 2, Ty(0))); assert(midpoint(-limits::denorm_min(), -limits::max()) == -limits::max() / 2); + + assert(check_feexcept(0)); } #endif // _M_FP_STRICT - assert(midpoint(limits::denorm_min(), Ty(1.0)) == (limits::denorm_min() + Ty(1.0)) / Ty(2.0)); - assert(midpoint(limits::denorm_min(), limits::max()) - == (limits::denorm_min() + limits::max()) / Ty(2.0)); - assert(midpoint(limits::denorm_min(), limits::lowest()) - == (limits::denorm_min() + limits::lowest()) / Ty(2.0)); - assert(midpoint(limits::denorm_min(), limits::infinity()) == limits::infinity()); - assert(midpoint(limits::denorm_min(), -limits::infinity()) == -limits::infinity()); - - assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), Ty(0))); - assert_bitwise_equal(mint_nan(0, 1), midpoint(Ty(0), mint_nan(0, 1))); - assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), limits::max())); - assert_bitwise_equal(mint_nan(0, 1), midpoint(limits::max(), mint_nan(0, 1))); - assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), mint_nan(0, 2))); - - assert(isnan(midpoint(-limits::infinity(), limits::infinity()))); - assert(isnan(midpoint(limits::quiet_NaN(), Ty(2.0)))); - assert(isnan(midpoint(Ty(2.0), limits::quiet_NaN()))); - assert(isnan(midpoint(limits::quiet_NaN(), limits::quiet_NaN()))); + { + ExceptGuard except; + + assert(midpoint(limits::denorm_min(), Ty(1.0)) == (limits::denorm_min() + Ty(1.0)) / Ty(2.0)); + assert(midpoint(limits::denorm_min(), limits::max()) + == (limits::denorm_min() + limits::max()) / Ty(2.0)); + assert(midpoint(limits::denorm_min(), limits::lowest()) + == (limits::denorm_min() + limits::lowest()) / Ty(2.0)); + assert(midpoint(limits::denorm_min(), limits::infinity()) == limits::infinity()); + assert(midpoint(limits::denorm_min(), -limits::infinity()) == -limits::infinity()); + + assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), Ty(0))); + assert_bitwise_equal(mint_nan(0, 1), midpoint(Ty(0), mint_nan(0, 1))); + assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), limits::max())); + assert_bitwise_equal(mint_nan(0, 1), midpoint(limits::max(), mint_nan(0, 1))); + assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), mint_nan(0, 1))); + + assert(isnan(midpoint(limits::quiet_NaN(), Ty(2.0)))); + assert(isnan(midpoint(Ty(2.0), limits::quiet_NaN()))); + assert(isnan(midpoint(limits::quiet_NaN(), limits::quiet_NaN()))); + + assert(check_feexcept(0)); + } + + // cases where midpoint() should raise FE_INVALID and return NaN + constexpr auto test_midpoint_fe_invalid = [](const Ty& a, const Ty& b) { + ExceptGuard except; + const auto answer = midpoint(a, b); + return check_feexcept(FE_INVALID) && isnan(answer); + }; + + Ty snan; + make_snan(snan); + + assert(test_midpoint_fe_invalid(-limits::infinity(), limits::infinity())); + assert(test_midpoint_fe_invalid(snan, limits::quiet_NaN())); + assert(test_midpoint_fe_invalid(limits::quiet_NaN(), snan)); + assert(test_midpoint_fe_invalid(snan, snan)); } template @@ -496,7 +594,7 @@ constexpr bool test_midpoint_pointer() { } template -int cmp(const Ty x, const Ty y) { +constexpr int cmp(const Ty x, const Ty y) { if (x > y) { return 1; } else if (x < y) { @@ -519,11 +617,12 @@ struct LerpNaNTestCase { Ty x; Ty y; Ty t; + optional expected_list[3] = {}; }; template struct LerpCases { // TRANSITION, VSO-934633 - static inline const LerpTestCase lerpTestCases[] = { + static inline constexpr LerpTestCase lerpTestCases[] = { {Ty(-1.0), Ty(1.0), Ty(2.0), Ty(3.0)}, {Ty(0.0), Ty(1.0), Ty(2.0), Ty(2.0)}, {Ty(-1.0), Ty(0.0), Ty(2.0), Ty(1.0)}, @@ -564,8 +663,8 @@ struct LerpCases { // TRANSITION, VSO-934633 // double: lerp(-0x0.0000000000001p-1022, -0x1.0000000000001p+0, 0.5) = -0x1.0000000000001p-1 {-limits::denorm_min(), constants::NegOneMinusUlp, Ty(0.5), -constants::PointFivePlusUlp}, - {Ty(1.0), constants::OnePlusUlp, nextafter(Ty(0.5), Ty(-1.0)), Ty(1.0)}, - {Ty(-1.0), constants::NegOneMinusUlp, nextafter(Ty(0.5), Ty(-1.0)), Ty(-1.0)}, + {Ty(1.0), constants::OnePlusUlp, constants::PointFiveMinusUlp, Ty(1.0)}, + {Ty(-1.0), constants::NegOneMinusUlp, constants::PointFiveMinusUlp, Ty(-1.0)}, {Ty(1.0), constants::OnePlusUlp, Ty(0.5), Ty(1.0)}, {Ty(-1.0), constants::NegOneMinusUlp, Ty(0.5), Ty(-1.0)}, @@ -586,8 +685,6 @@ struct LerpCases { // TRANSITION, VSO-934633 {Ty(1.0), Ty(2.0), constants::TwoPlusUlp, constants::TwoPlusUlp + Ty(1.0)}, {Ty(1.0), Ty(2.0), Ty(0.5), constants::PointFivePlusUlp + Ty(1.0)}, - {limits::lowest(), limits::max(), Ty(2.0), limits::infinity()}, - {limits::max(), limits::lowest(), Ty(2.0), -limits::infinity()}, {limits::max(), limits::max(), Ty(2.0), limits::max()}, {limits::lowest(), limits::lowest(), Ty(2.0), limits::lowest()}, @@ -598,84 +695,192 @@ struct LerpCases { // TRANSITION, VSO-934633 {limits::denorm_min(), limits::infinity(), Ty(0.5), limits::infinity()}, {limits::denorm_min(), -limits::infinity(), Ty(0.5), -limits::infinity()}, - // the following handling of NaNs and infinities isn't in the spec, but seems like the right behavior: + // the following handling of infinities isn't in the spec, but seems like the right behavior: - // when the inputs are infinity and T is 1 or more, return the second parameter + // if the values differ and T is an infinity, the appropriate infinity according to direction + {Ty(0), Ty(1), limits::infinity(), limits::infinity()}, + {Ty(0), Ty(1), -limits::infinity(), -limits::infinity()}, + {Ty(0), -Ty(1), limits::infinity(), -limits::infinity()}, + {Ty(0), -Ty(1), -limits::infinity(), limits::infinity()}, + + // when the inputs are infinity of the same sign and 0 < T < 1, return that infinity + {limits::infinity(), limits::infinity(), limits::denorm_min(), limits::infinity()}, + {-limits::infinity(), -limits::infinity(), limits::denorm_min(), -limits::infinity()}, + {limits::infinity(), limits::infinity(), Ty(0.5), limits::infinity()}, + {-limits::infinity(), -limits::infinity(), Ty(0.5), -limits::infinity()}, + {limits::infinity(), limits::infinity(), constants::OneMinusUlp, limits::infinity()}, + {-limits::infinity(), -limits::infinity(), constants::OneMinusUlp, -limits::infinity()}, + + // when the inputs are infinity of opposite signs and T > 1, return the second parameter {-limits::infinity(), limits::infinity(), constants::OnePlusUlp, limits::infinity()}, {limits::infinity(), -limits::infinity(), constants::OnePlusUlp, -limits::infinity()}, - {-limits::infinity(), limits::infinity(), Ty(1.0), limits::infinity()}, - {limits::infinity(), -limits::infinity(), Ty(1.0), -limits::infinity()}, + {-limits::infinity(), limits::infinity(), Ty(2.0), limits::infinity()}, + {limits::infinity(), -limits::infinity(), Ty(2.0), -limits::infinity()}, + {-limits::infinity(), limits::infinity(), limits::max(), limits::infinity()}, + {limits::infinity(), -limits::infinity(), limits::max(), -limits::infinity()}, + {-limits::infinity(), limits::infinity(), limits::infinity(), limits::infinity()}, + {limits::infinity(), -limits::infinity(), limits::infinity(), -limits::infinity()}, - // when the inputs are infinity and T is 0 or less, return the first parameter + // when the inputs are infinity of opposite signs and T < 0, return the first parameter + {-limits::infinity(), limits::infinity(), -limits::infinity(), -limits::infinity()}, + {limits::infinity(), -limits::infinity(), -limits::infinity(), limits::infinity()}, + {-limits::infinity(), limits::infinity(), -limits::max(), -limits::infinity()}, + {limits::infinity(), -limits::infinity(), -limits::max(), limits::infinity()}, + {-limits::infinity(), limits::infinity(), -Ty(2.0), -limits::infinity()}, + {limits::infinity(), -limits::infinity(), -Ty(2.0), limits::infinity()}, {-limits::infinity(), limits::infinity(), -limits::denorm_min(), -limits::infinity()}, {limits::infinity(), -limits::infinity(), -limits::denorm_min(), limits::infinity()}, - {-limits::infinity(), limits::infinity(), Ty(0), -limits::infinity()}, - {limits::infinity(), -limits::infinity(), Ty(0), limits::infinity()}, - - // if any of the inputs are NaN, return the first NaN - {mint_nan(0, 42), mint_nan(1, 42), mint_nan(0, 1729), mint_nan(0, 42)}, - {Ty(1.0), mint_nan(1, 42), mint_nan(0, 1729), mint_nan(1, 42)}, - {mint_nan(1, 42), Ty(1.0), mint_nan(0, 1729), mint_nan(1, 42)}, - {Ty(1.0), Ty(1.0), mint_nan(0, 1729), mint_nan(0, 1729)}, - - {limits::infinity(), mint_nan(1, 42), mint_nan(0, 1729), mint_nan(1, 42)}, - {mint_nan(1, 42), limits::infinity(), mint_nan(0, 1729), mint_nan(1, 42)}, - {limits::infinity(), limits::infinity(), mint_nan(0, 1729), mint_nan(0, 1729)}, - // if the values differ and T is an infinity, the appropriate infinity according to direction - {Ty(0), Ty(1), limits::infinity(), limits::infinity()}, - {Ty(0), Ty(1), -limits::infinity(), -limits::infinity()}, - - {Ty(0), -Ty(1), limits::infinity(), -limits::infinity()}, - {Ty(0), -Ty(1), -limits::infinity(), limits::infinity()}, + // if a is an infinity, b is finite and T != 1, return that infinity or the other according to "direction" of t + {limits::infinity(), Ty(1.0), -limits::infinity(), limits::infinity()}, + {limits::infinity(), Ty(1.0), -limits::max(), limits::infinity()}, + {limits::infinity(), Ty(1.0), -Ty(1.0), limits::infinity()}, + {limits::infinity(), Ty(1.0), -limits::denorm_min(), limits::infinity()}, + {limits::infinity(), Ty(1.0), -Ty(0.0), limits::infinity()}, + {limits::infinity(), Ty(1.0), Ty(0.0), limits::infinity()}, + {limits::infinity(), Ty(1.0), limits::denorm_min(), limits::infinity()}, + {limits::infinity(), Ty(1.0), Ty(0.5), limits::infinity()}, + {limits::infinity(), Ty(1.0), constants::OneMinusUlp, limits::infinity()}, - // if one of a or b is an infinity, choose the other value when t says exact, otherwise - // return that infinity or the other according to "direction" of t - {limits::infinity(), Ty(1.0), Ty(1.0), Ty(1.0)}, {limits::infinity(), Ty(1.0), constants::OnePlusUlp, -limits::infinity()}, - {limits::infinity(), Ty(1.0), constants::OneMinusUlp, limits::infinity()}, - {limits::infinity(), Ty(1.0), limits::denorm_min(), limits::infinity()}, - {limits::infinity(), Ty(1.0), Ty(0.0), limits::infinity()}, + {limits::infinity(), Ty(1.0), Ty(2.0), -limits::infinity()}, + {limits::infinity(), Ty(1.0), limits::max(), -limits::infinity()}, + {limits::infinity(), Ty(1.0), limits::infinity(), -limits::infinity()}, + + {-limits::infinity(), Ty(1.0), -limits::infinity(), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), -limits::max(), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), -Ty(1.0), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), -limits::denorm_min(), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), -Ty(0.0), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), Ty(0.0), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), limits::denorm_min(), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), Ty(0.5), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), constants::OneMinusUlp, -limits::infinity()}, - {-limits::infinity(), Ty(1.0), Ty(1.0), Ty(1.0)}, {-limits::infinity(), Ty(1.0), constants::OnePlusUlp, limits::infinity()}, - {-limits::infinity(), Ty(1.0), constants::OneMinusUlp, -limits::infinity()}, - {-limits::infinity(), Ty(1.0), limits::denorm_min(), -limits::infinity()}, - {-limits::infinity(), Ty(1.0), Ty(0.0), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), Ty(2.0), limits::infinity()}, + {-limits::infinity(), Ty(1.0), limits::max(), limits::infinity()}, + {-limits::infinity(), Ty(1.0), limits::infinity(), limits::infinity()}, + + // if b is an infinity, a is finite and T != 0, return that infinity or the other according to "direction" of t + {Ty(1.0), limits::infinity(), -limits::infinity(), -limits::infinity()}, + {Ty(1.0), limits::infinity(), -limits::max(), -limits::infinity()}, + {Ty(1.0), limits::infinity(), -Ty(1.0), -limits::infinity()}, + {Ty(1.0), limits::infinity(), -limits::denorm_min(), -limits::infinity()}, - {Ty(1.0), limits::infinity(), Ty(0.0), Ty(1.0)}, - {Ty(1.0), limits::infinity(), -Ty(0.0), Ty(1.0)}, {Ty(1.0), limits::infinity(), limits::denorm_min(), limits::infinity()}, - {Ty(1.0), limits::infinity(), -limits::denorm_min(), -limits::infinity()}, + {Ty(1.0), limits::infinity(), Ty(0.5), limits::infinity()}, + {Ty(1.0), limits::infinity(), constants::OneMinusUlp, limits::infinity()}, + {Ty(1.0), limits::infinity(), Ty(1.0), limits::infinity()}, + {Ty(1.0), limits::infinity(), constants::OnePlusUlp, limits::infinity()}, + {Ty(1.0), limits::infinity(), Ty(2.0), limits::infinity()}, + {Ty(1.0), limits::infinity(), limits::max(), limits::infinity()}, + {Ty(1.0), limits::infinity(), limits::infinity(), limits::infinity()}, + + {Ty(1.0), -limits::infinity(), -limits::infinity(), limits::infinity()}, + {Ty(1.0), -limits::infinity(), -limits::max(), limits::infinity()}, + {Ty(1.0), -limits::infinity(), -Ty(1.0), limits::infinity()}, + {Ty(1.0), -limits::infinity(), -limits::denorm_min(), limits::infinity()}, - {Ty(1.0), -limits::infinity(), Ty(0.0), Ty(1.0)}, - {Ty(1.0), -limits::infinity(), -Ty(0.0), Ty(1.0)}, {Ty(1.0), -limits::infinity(), limits::denorm_min(), -limits::infinity()}, - {Ty(1.0), -limits::infinity(), -limits::denorm_min(), limits::infinity()}, + {Ty(1.0), -limits::infinity(), Ty(0.5), -limits::infinity()}, + {Ty(1.0), -limits::infinity(), constants::OneMinusUlp, -limits::infinity()}, + {Ty(1.0), -limits::infinity(), Ty(1.0), -limits::infinity()}, + {Ty(1.0), -limits::infinity(), constants::OnePlusUlp, -limits::infinity()}, + {Ty(1.0), -limits::infinity(), Ty(2.0), -limits::infinity()}, + {Ty(1.0), -limits::infinity(), limits::max(), -limits::infinity()}, + {Ty(1.0), -limits::infinity(), limits::infinity(), -limits::infinity()}, + }; - // if all 3 values are infinities, choose the selected one according to t - {limits::infinity(), limits::infinity(), limits::infinity(), limits::infinity()}, - {limits::infinity(), limits::infinity(), -limits::infinity(), limits::infinity()}, - {limits::infinity(), -limits::infinity(), limits::infinity(), -limits::infinity()}, - {limits::infinity(), -limits::infinity(), -limits::infinity(), limits::infinity()}, - {-limits::infinity(), limits::infinity(), limits::infinity(), limits::infinity()}, - {-limits::infinity(), limits::infinity(), -limits::infinity(), -limits::infinity()}, - {-limits::infinity(), -limits::infinity(), limits::infinity(), -limits::infinity()}, - {-limits::infinity(), -limits::infinity(), -limits::infinity(), -limits::infinity()}, + static inline constexpr LerpTestCase lerpOverflowTestCases[] = { + {limits::lowest(), limits::max(), Ty(2.0), limits::infinity()}, + {limits::max(), limits::lowest(), Ty(2.0), -limits::infinity()}, }; - static inline constexpr LerpNaNTestCase lerpNaNTestCases[] = { - // when the inputs are infinity and T is between 0 and 1, return NaN + static inline constexpr LerpNaNTestCase lerpInvalidTestCases[] = { + // if the values are equal and T is an infinity, NaN + {Ty(0), Ty(0), limits::infinity()}, + {Ty(0), Ty(0), -limits::infinity()}, + + // when the inputs are infinity of the same sign and T <= 0, return NaN + {limits::infinity(), limits::infinity(), -limits::infinity()}, + {-limits::infinity(), -limits::infinity(), -limits::infinity()}, + {limits::infinity(), limits::infinity(), -limits::max()}, + {-limits::infinity(), -limits::infinity(), -limits::max()}, + {limits::infinity(), limits::infinity(), -Ty(1.0)}, + {-limits::infinity(), -limits::infinity(), -Ty(1.0)}, + {limits::infinity(), limits::infinity(), -limits::denorm_min()}, + {-limits::infinity(), -limits::infinity(), -limits::denorm_min()}, + {limits::infinity(), limits::infinity(), -Ty(0.0)}, + {-limits::infinity(), -limits::infinity(), -Ty(0.0)}, + {limits::infinity(), limits::infinity(), Ty(0.0)}, + {-limits::infinity(), -limits::infinity(), Ty(0.0)}, + + // when the inputs are infinity of the same sign and T >= 1, return NaN + {limits::infinity(), limits::infinity(), Ty(1.0)}, + {-limits::infinity(), -limits::infinity(), Ty(1.0)}, + {limits::infinity(), limits::infinity(), constants::OnePlusUlp}, + {-limits::infinity(), -limits::infinity(), constants::OnePlusUlp}, + {limits::infinity(), limits::infinity(), Ty(2.0)}, + {-limits::infinity(), -limits::infinity(), Ty(2.0)}, + {limits::infinity(), limits::infinity(), limits::max()}, + {-limits::infinity(), -limits::infinity(), limits::max()}, + {limits::infinity(), limits::infinity(), limits::infinity()}, + {-limits::infinity(), -limits::infinity(), limits::infinity()}, + + // when the inputs are infinity of opposite signs and 0 <= T <= 1, return NaN + {-limits::infinity(), limits::infinity(), -Ty(0.0)}, + {limits::infinity(), -limits::infinity(), -Ty(0.0)}, + {-limits::infinity(), limits::infinity(), Ty(0.0)}, + {limits::infinity(), -limits::infinity(), Ty(0.0)}, {-limits::infinity(), limits::infinity(), limits::denorm_min()}, {limits::infinity(), -limits::infinity(), limits::denorm_min()}, - {-limits::infinity(), limits::infinity(), constants::OneMinusUlp}, - {limits::infinity(), -limits::infinity(), constants::OneMinusUlp}, {-limits::infinity(), limits::infinity(), Ty(0.5)}, {limits::infinity(), -limits::infinity(), Ty(0.5)}, + {-limits::infinity(), limits::infinity(), constants::OneMinusUlp}, + {limits::infinity(), -limits::infinity(), constants::OneMinusUlp}, + {-limits::infinity(), limits::infinity(), Ty(1.0)}, + {limits::infinity(), -limits::infinity(), Ty(1.0)}, + + // if a is an infinity, b is finite and T = 1, return NaN + {limits::infinity(), Ty(1.0), Ty(1.0)}, + {-limits::infinity(), Ty(1.0), Ty(1.0)}, + + // if b is an infinity, a is finite and T = 0, return NaN + {Ty(1.0), limits::infinity(), Ty(0.0)}, + {Ty(1.0), limits::infinity(), -Ty(0.0)}, + {Ty(1.0), -limits::infinity(), Ty(0.0)}, + {Ty(1.0), -limits::infinity(), -Ty(0.0)}, + }; - // if the values are equal and T is an infinity, NaN - {Ty(0), Ty(0), limits::infinity()}, - {Ty(0), Ty(0), -limits::infinity()}, + static inline constexpr LerpNaNTestCase lerpNaNTestCases[] = { + {mint_nan(0, 42), mint_nan(1, 42), mint_nan(0, 1729), + {mint_nan(0, 42), mint_nan(1, 42), mint_nan(0, 1729)}}, + {Ty(1.0), mint_nan(1, 42), mint_nan(0, 1729), {mint_nan(1, 42), mint_nan(0, 1729)}}, + {mint_nan(1, 42), Ty(1.0), mint_nan(0, 1729), {mint_nan(1, 42), mint_nan(0, 1729)}}, + {Ty(1.0), Ty(1.0), mint_nan(0, 1729), {mint_nan(0, 1729)}}, + + {limits::infinity(), mint_nan(1, 42), mint_nan(0, 1729), + {mint_nan(1, 42), mint_nan(0, 1729)}}, + {mint_nan(1, 42), limits::infinity(), mint_nan(0, 1729), + {mint_nan(1, 42), mint_nan(0, 1729)}}, + {limits::infinity(), limits::infinity(), mint_nan(0, 1729), {mint_nan(0, 1729)}}, + + {mint_nan(0, 42), mint_nan(1, 42), -Ty(0.0), {mint_nan(0, 42), mint_nan(1, 42)}}, + {mint_nan(0, 42), mint_nan(1, 42), Ty(0.0), {mint_nan(0, 42), mint_nan(1, 42)}}, + {mint_nan(0, 42), mint_nan(1, 42), Ty(1.0), {mint_nan(0, 42), mint_nan(1, 42)}}, + {mint_nan(0, 42), Ty(1.0), -Ty(0.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), Ty(1.0), Ty(0.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), Ty(1.0), Ty(1.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), limits::infinity(), -Ty(0.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), limits::infinity(), Ty(0.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), limits::infinity(), Ty(1.0), {mint_nan(0, 42)}}, + {Ty(1.0), mint_nan(1, 42), -Ty(0.0), {mint_nan(1, 42)}}, + {Ty(1.0), mint_nan(1, 42), Ty(0.0), {mint_nan(1, 42)}}, + {Ty(1.0), mint_nan(1, 42), Ty(1.0), {mint_nan(1, 42)}}, + {limits::infinity(), mint_nan(1, 42), -Ty(0.0), {mint_nan(1, 42)}}, + {limits::infinity(), mint_nan(1, 42), Ty(0.0), {mint_nan(1, 42)}}, + {limits::infinity(), mint_nan(1, 42), Ty(1.0), {mint_nan(1, 42)}}, }; }; @@ -730,25 +935,93 @@ bool test_lerp() { STATIC_ASSERT(is_signed_v); STATIC_ASSERT(noexcept(lerp(Ty(), Ty(), Ty()))); - // No constexpr tests for now because implementing constexpr lerp appears to depend on p0553, which was not accepted - // (yet?). + constexpr auto test_lerp_constexpr = [] { + using bit_type = conditional_t; + + for (const auto& testCase : LerpCases::lerpTestCases) { + const auto answer = lerp(testCase.x, testCase.y, testCase.t); + assert(bit_cast(answer) == bit_cast(testCase.expected)); + } + + for (auto&& testCase : LerpCases::lerpNaNTestCases) { + const auto answer = lerp(testCase.x, testCase.y, testCase.t); + assert(any_of(begin(testCase.expected_list), end(testCase.expected_list), [&](const auto& expected) { + return expected.has_value() && bit_cast(answer) == bit_cast(expected.value()); + })); + } + + return true; + }; + + STATIC_ASSERT(test_lerp_constexpr()); for (auto&& testCase : LerpCases::lerpTestCases) { + ExceptGuard except; const auto answer = lerp(testCase.x, testCase.y, testCase.t); - if (memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) { + if (!check_feexcept(0) || memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) { + print_lerp_result(testCase, answer); + abort(); + } + } + + for (auto&& testCase : LerpCases::lerpOverflowTestCases) { + ExceptGuard except; + const auto answer = lerp(testCase.x, testCase.y, testCase.t); + if (!check_feexcept(FE_OVERFLOW) || memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) { + print_lerp_result(testCase, answer); + abort(); + } + } + + for (auto&& testCase : LerpCases::lerpInvalidTestCases) { + ExceptGuard except; + const auto answer = lerp(testCase.x, testCase.y, testCase.t); + if (!check_feexcept(FE_INVALID) || !isnan(answer)) { print_lerp_result(testCase, answer); abort(); } } for (auto&& testCase : LerpCases::lerpNaNTestCases) { + ExceptGuard except; const auto answer = lerp(testCase.x, testCase.y, testCase.t); - if (!isnan(answer)) { + if (!check_feexcept(0) + || none_of(begin(testCase.expected_list), end(testCase.expected_list), [&answer](const auto& expected) { + return expected.has_value() && memcmp(&answer, &expected.value(), sizeof(Ty)) == 0; + })) { print_lerp_result(testCase, answer); abort(); } } + constexpr auto test_lerp_snan = [](const Ty& a, const Ty& b, const Ty& t) { + ExceptGuard except; + const auto answer = lerp(a, b, t); + return check_feexcept(FE_INVALID) && isnan(answer); + }; + + Ty snan; + make_snan(snan); + + assert(test_lerp_snan(snan, limits::quiet_NaN(), limits::quiet_NaN())); + assert(test_lerp_snan(limits::quiet_NaN(), snan, limits::quiet_NaN())); + assert(test_lerp_snan(snan, snan, limits::quiet_NaN())); + assert(test_lerp_snan(limits::quiet_NaN(), limits::quiet_NaN(), snan)); + assert(test_lerp_snan(snan, limits::quiet_NaN(), snan)); + assert(test_lerp_snan(limits::quiet_NaN(), snan, snan)); + assert(test_lerp_snan(snan, snan, snan)); + + assert(test_lerp_snan(Ty{0}, Ty{0}, snan)); + assert(test_lerp_snan(Ty{1}, Ty{1}, snan)); + assert(test_lerp_snan(Ty{0}, snan, Ty{0})); + assert(test_lerp_snan(Ty{0}, snan, Ty{1})); + assert(test_lerp_snan(snan, Ty{0}, Ty{0})); + assert(test_lerp_snan(snan, Ty{0}, Ty{1})); + + STATIC_ASSERT(cmp(lerp(Ty(1.0), Ty(2.0), Ty(4.0)), lerp(Ty(1.0), Ty(2.0), Ty(3.0))) * cmp(Ty(4.0), Ty(3.0)) + * cmp(Ty(2.0), Ty(1.0)) + >= 0); + assert(cmp(lerp(Ty(1.0), Ty(2.0), Ty(4.0)), lerp(Ty(1.0), Ty(2.0), Ty(3.0))) * cmp(Ty(4.0), Ty(3.0)) * cmp(Ty(2.0), Ty(1.0)) >= 0); @@ -803,9 +1076,13 @@ int main() { STATIC_ASSERT(test_midpoint_floating_constexpr()); STATIC_ASSERT(test_midpoint_floating_constexpr()); - test_midpoint_floating_constexpr(); - test_midpoint_floating_constexpr(); - test_midpoint_floating_constexpr(); + { + ExceptGuard except; + test_midpoint_floating_constexpr(); + test_midpoint_floating_constexpr(); + test_midpoint_floating_constexpr(); + assert(check_feexcept(0)); + } test_midpoint_floating(); test_midpoint_floating(); diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp index 3c374328a10..e1fc7753df4 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp @@ -685,6 +685,20 @@ STATIC_ASSERT(__cpp_lib_integer_sequence == 201304L); STATIC_ASSERT(__cpp_lib_integral_constant_callable == 201304L); #endif +#if _HAS_CXX20 +#ifndef __cpp_lib_interpolate +#error __cpp_lib_interpolate is not defined +#elif __cpp_lib_interpolate != 201902L +#error __cpp_lib_interpolate is not 201902L +#else +STATIC_ASSERT(__cpp_lib_interpolate == 201902L); +#endif +#else +#ifdef __cpp_lib_interpolate +#error __cpp_lib_interpolate is defined +#endif +#endif + #ifndef __cpp_lib_invoke #error __cpp_lib_invoke is not defined #elif __cpp_lib_invoke != 201411L From 30777d5adf9d061c2d151a4f85b27c60012c9ab3 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Wed, 29 Jul 2020 20:57:47 -0700 Subject: [PATCH 23/50] Fix thinko in ranges::iter_swap (#1072) ...by transposing arguments to `_Iter_exchange_move`. Fixes #1067 --- stl/inc/xutility | 12 ++++----- .../test.cpp | 27 +++++++++++++++---- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index fd38d518b3c..b22d75d57ef 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -1038,10 +1038,10 @@ namespace ranges { // clang-format on template - _NODISCARD constexpr iter_value_t> _Iter_exchange_move( - _Xty&& _XVal, _Yty&& _YVal) noexcept(noexcept(iter_value_t>(iter_move(_XVal)))) { - iter_value_t> _Tmp(iter_move(_XVal)); - *_XVal = iter_move(_YVal); + _NODISCARD constexpr iter_value_t> _Iter_exchange_move(_Xty&& _XVal, + _Yty&& _YVal) noexcept(noexcept(iter_value_t>(_RANGES iter_move(_XVal)))) { + iter_value_t> _Tmp(_RANGES iter_move(_XVal)); + *_XVal = _RANGES iter_move(_YVal); return _Tmp; } @@ -1057,7 +1057,7 @@ namespace ranges { return {_St::_Swap, noexcept(_RANGES swap(*_STD declval<_Ty1>(), *_STD declval<_Ty2>()))}; } else if constexpr (_Symmetric_indirectly_movable_storable<_Ty1, _Ty2>) { constexpr auto _Nothrow = noexcept( - *_STD declval<_Ty1>() = _Iter_exchange_move(_STD declval<_Ty1>(), _STD declval<_Ty2>())); + *_STD declval<_Ty1>() = _Iter_exchange_move(_STD declval<_Ty2>(), _STD declval<_Ty1>())); return {_St::_Exchange, _Nothrow}; } else { return {_St::_None}; @@ -1078,7 +1078,7 @@ namespace ranges { _RANGES swap(*static_cast<_Ty1&&>(_Val1), *static_cast<_Ty2&&>(_Val2)); } else if constexpr (_Choice<_Ty1, _Ty2>._Strategy == _St::_Exchange) { *static_cast<_Ty1&&>(_Val1) = - _Iter_exchange_move(static_cast<_Ty1&&>(_Val1), static_cast<_Ty2&&>(_Val2)); + _Iter_exchange_move(static_cast<_Ty2&&>(_Val2), static_cast<_Ty1&&>(_Val1)); } else { static_assert(_Always_false<_Ty1>, "should be unreachable"); } diff --git a/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp index bdb19a25b0a..f571db14fd8 100644 --- a/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp @@ -1267,11 +1267,28 @@ namespace iterator_cust_swap_test { // clang-format on constexpr bool test() { - int i0 = 42, i1 = 13; - S s0{i0}, s1{i1}; - ranges::iter_swap(s0, s1); - assert(i0 == 13); - assert(i1 == 42); + { + int i0 = 42; + int i1 = 13; + S s0{i0}; + S s1{i1}; + ranges::iter_swap(s0, s1); + assert(i0 == 13); + assert(i1 == 42); + } + +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 + if (!std::is_constant_evaluated()) +#endif // TRANSITION, VSO-938163 + { + // Validate iter_swap bullet 3 to defend against regression of GH-1067 "ranges::iter_swap is broken" + int i = 42; + long l = 13; + ranges::iter_swap(&i, &l); + assert(i == 13); + assert(l == 42); + } + return true; } STATIC_ASSERT(test()); From 58a8a39b51becb5c7e63b67442cfb1b343731cb4 Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Thu, 30 Jul 2020 06:11:32 +0200 Subject: [PATCH 24/50] Implement ranges::rotate and ranges::rotate_copy (#1073) Co-authored-by: Casey Carter --- stl/inc/algorithm | 176 +++++++++++++++++- tests/std/test.lst | 2 + .../tests/P0896R4_ranges_alg_rotate/env.lst | 4 + .../tests/P0896R4_ranges_alg_rotate/test.cpp | 52 ++++++ .../P0896R4_ranges_alg_rotate_copy/env.lst | 4 + .../P0896R4_ranges_alg_rotate_copy/test.cpp | 56 ++++++ 6 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_rotate/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_rotate/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_rotate_copy/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_rotate_copy/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 27099d60c70..ed4466cfcd8 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -4897,7 +4897,7 @@ namespace ranges { auto _UFirst = _Get_unwrapped(_STD move(_First)); auto _ULast = _Get_final_iterator_unwrapped<_It>(_UFirst, _STD move(_Last)); _Seek_wrapped(_First, _ULast); - _RANGES _Reverse_common(_STD move(_UFirst), _STD move(_ULast)); + _Reverse_common(_STD move(_UFirst), _STD move(_ULast)); return _First; } @@ -4905,7 +4905,7 @@ namespace ranges { requires permutable> constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range) const { auto _ULast = _Get_final_iterator_unwrapped(_Range); - _RANGES _Reverse_common(_Ubegin(_Range), _ULast); + _Reverse_common(_Ubegin(_Range), _ULast); return _Rewrap_iterator(_Range, _STD move(_ULast)); } // clang-format on @@ -4972,6 +4972,119 @@ _FwdIt reverse_copy(_ExPo&&, _BidIt _First, _BidIt _Last, _FwdIt _Dest) noexcept } #endif // _HAS_CXX17 +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::rotate + class _Rotate_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se> + constexpr subrange<_It> operator()(_It _First, _It _Mid, _Se _Last) const { + _Adl_verify_range(_First, _Mid); + _Adl_verify_range(_Mid, _Last); + auto _UResult = _Rotate_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Mid)), _Get_unwrapped(_STD move(_Last))); + + return _Rewrap_subrange>(_First, _STD move(_UResult)); + } + + // clang-format off + template + requires permutable> + constexpr borrowed_subrange_t<_Rng> operator()(_Rng&& _Range, iterator_t<_Rng> _Mid) const { + // clang-format on + _Adl_verify_range(_RANGES begin(_Range), _Mid); + _Adl_verify_range(_Mid, _RANGES end(_Range)); + auto _UResult = _Rotate_unchecked(_Ubegin(_Range), _Get_unwrapped(_STD move(_Mid)), _Uend(_Range)); + + return _Rewrap_subrange>(_Mid, _STD move(_UResult)); + } + + private: + template + _NODISCARD static constexpr subrange<_It> _Rotate_unchecked(_It _First, _It _Mid, _Se _Last) { + // Exchange the ranges [_First, _Mid) and [_Mid, _Last) + // that is, rotates [_First, _Last) left by distance(_First, _Mid) positions + _STL_INTERNAL_STATIC_ASSERT(permutable<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + + if (_First == _Mid) { + auto _Final = _Get_final_iterator_unwrapped<_It>(_Mid, _STD move(_Last)); + return {_Final, _Final}; + } + + if (_Mid == _Last) { + return {_STD move(_First), _STD move(_Mid)}; + } + + if constexpr (bidirectional_iterator<_It>) { + _Reverse_common(_First, _Mid); + auto _Final = _Get_final_iterator_unwrapped<_It>(_Mid, _STD move(_Last)); + _Reverse_common(_Mid, _Final); + + if constexpr (random_access_iterator<_It>) { + _Reverse_common(_First, _Final); + _First += _Final - _Mid; + + return {_STD move(_First), _STD move(_Final)}; + } else { + auto [_Mid_first, _Mid_last] = _Reverse_until_mid_unchecked(_STD move(_First), _Mid, _Final); + _Reverse_common(_Mid_first, _Mid_last); + + if (_Mid_first == _Mid) { + return {_STD move(_Mid_last), _STD move(_Final)}; + } else { + return {_STD move(_Mid_first), _STD move(_Final)}; + } + } + } else { + auto _Next = _Mid; + do { // rotate the first cycle + _RANGES iter_swap(_First, _Next); + ++_First; + ++_Next; + if (_First == _Mid) { + _Mid = _Next; + } + } while (_Next != _Last); + + auto _Begin = _First; + + while (_Mid != _Last) { // rotate subsequent cycles + _Next = _Mid; + do { + _RANGES iter_swap(_First, _Next); + ++_First; + ++_Next; + if (_First == _Mid) { + _Mid = _Next; + } + } while (_Next != _Last); + } + return {_STD move(_Begin), _STD move(_Mid)}; + } + } + + template + _NODISCARD static constexpr subrange<_It> _Reverse_until_mid_unchecked(_It _First, const _It _Mid, _It _Last) { + // reverse until either _First or _Last hits _Mid + _STL_INTERNAL_STATIC_ASSERT(permutable<_It>); + _STL_INTERNAL_CHECK(_First != _Mid); + _STL_INTERNAL_CHECK(_Mid != _Last); + + do { + _RANGES iter_swap(_First, --_Last); + } while (++_First != _Mid && _Last != _Mid); + + return {_STD move(_First), _STD move(_Last)}; + } + }; + + inline constexpr _Rotate_fn rotate{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE rotate_copy template _CONSTEXPR20 _OutIt rotate_copy(_FwdIt _First, _FwdIt _Mid, _FwdIt _Last, _OutIt _Dest) { @@ -4996,6 +5109,65 @@ _FwdIt2 rotate_copy(_ExPo&&, _FwdIt1 _First, _FwdIt1 _Mid, _FwdIt1 _Last, _FwdIt return _STD rotate_copy(_First, _Mid, _Last, _Dest); } +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE rotate_copy_result + template + using rotate_copy_result = in_out_result<_In, _Out>; + + // VARIABLE ranges::rotate_copy + class _Rotate_copy_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out> + requires indirectly_copyable<_It, _Out> + constexpr rotate_copy_result<_It, _Out> operator()(_It _First, _It _Mid, _Se _Last, _Out _Result) const { + // clang-format on + _Adl_verify_range(_First, _Mid); + _Adl_verify_range(_Mid, _Last); + auto _UResult = _Rotate_copy_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Mid)), + _Get_unwrapped(_STD move(_Last)), _STD move(_Result)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.out)}; + } + + // clang-format off + template + requires indirectly_copyable, _Out> + constexpr rotate_copy_result, _Out> operator()( + _Rng&& _Range, iterator_t<_Rng> _Mid, _Out _Result) const { + // clang-format on + _Adl_verify_range(_RANGES begin(_Range), _Mid); + _Adl_verify_range(_Mid, _RANGES end(_Range)); + auto _UResult = _Rotate_copy_unchecked( + _Ubegin(_Range), _Get_unwrapped(_STD move(_Mid)), _Uend(_Range), _STD move(_Result)); + + return {_Rewrap_iterator(_Range, _STD move(_UResult.in)), _STD move(_UResult.out)}; + } + + private: + template + _NODISCARD static constexpr rotate_copy_result<_It, _Out> _Rotate_copy_unchecked( + _It _First, _It _Mid, _Se _Last, _Out _Result) { + // Copy the content of [_Mid, _Last) and [_First, _Mid) to _Result + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_copyable<_It, _Out>); + + auto _UResult1 = _RANGES _Copy_unchecked(_Mid, _STD move(_Last), _STD move(_Result)); + auto _UResult2 = _RANGES _Copy_unchecked(_STD move(_First), _STD move(_Mid), _STD move(_UResult1.out)); + return {_STD move(_UResult1.in), _STD move(_UResult2.out)}; + } + }; + + inline constexpr _Rotate_copy_fn rotate_copy{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE sample template _SampleIt _Sample_reservoir_unchecked( diff --git a/tests/std/test.lst b/tests/std/test.lst index 9ce41e6b7c2..04b21077e19 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -282,6 +282,8 @@ tests\P0896R4_ranges_alg_replace_copy tests\P0896R4_ranges_alg_replace_copy_if tests\P0896R4_ranges_alg_replace_if tests\P0896R4_ranges_alg_reverse +tests\P0896R4_ranges_alg_rotate +tests\P0896R4_ranges_alg_rotate_copy tests\P0896R4_ranges_alg_sample tests\P0896R4_ranges_alg_search tests\P0896R4_ranges_alg_search_n diff --git a/tests/std/tests/P0896R4_ranges_alg_rotate/env.lst b/tests/std/tests/P0896R4_ranges_alg_rotate/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_rotate/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_rotate/test.cpp b/tests/std/tests/P0896R4_ranges_alg_rotate/test.cpp new file mode 100644 index 00000000000..511a83c0b98 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_rotate/test.cpp @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include +using namespace std; +using P = pair; + +// Validate dangling story +STATIC_ASSERT(same_as{}, nullptr_to)), ranges::dangling>); +STATIC_ASSERT(same_as{}, nullptr_to)), ranges::subrange>); + +struct instantiator { + static constexpr P expected[5] = {{3, 47}, {4, 99}, {0, 99}, {1, 47}, {2, 99}}; + + template + static constexpr void call() { + using ranges::rotate, ranges::subrange, ranges::equal, ranges::iterator_t; + { // Validate iterator overload + P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; + ReadWrite wrapped_input{input}; + + auto result = rotate(wrapped_input.begin(), next(wrapped_input.begin(), 3), wrapped_input.end()); + STATIC_ASSERT(same_as>>); + assert(result.begin() == next(wrapped_input.begin(), 2)); + assert(result.end() == wrapped_input.end()); + assert(equal(expected, input)); + } + { // Validate range overload + P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}}; + ReadWrite wrapped_input{input}; + + auto result = rotate(wrapped_input, next(wrapped_input.begin(), 3)); + STATIC_ASSERT(same_as>>); + assert(result.begin() == next(wrapped_input.begin(), 2)); + assert(result.end() == wrapped_input.end()); + assert(equal(expected, input)); + } + } +}; + +int main() { +#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-938163 + STATIC_ASSERT((test_fwd(), true)); +#endif // TRANSITION, VSO-938163 + test_fwd(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_rotate_copy/env.lst b/tests/std/tests/P0896R4_ranges_alg_rotate_copy/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_rotate_copy/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_rotate_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_rotate_copy/test.cpp new file mode 100644 index 00000000000..a28336063ae --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_rotate_copy/test.cpp @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +#include +using namespace std; + +// Validate that rotate_copy_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, nullptr_to, nullptr_to)), + ranges::rotate_copy_result>); +STATIC_ASSERT(same_as{}, nullptr_to, nullptr_to)), + ranges::rotate_copy_result>); + +struct instantiator { + static constexpr int input[5] = {1, 2, 3, 4, 5}; + static constexpr int expected[5] = {4, 5, 1, 2, 3}; + + template > Write> + static constexpr void call() { + using ranges::rotate_copy, ranges::rotate_copy_result, ranges::equal, ranges::iterator_t; + { // Validate iterator overload + int output[5] = {-1, -1, -1, -1, -1}; + Read wrapped_input{input}; + iterator_t mid = next(wrapped_input.begin(), 3); + + auto result = rotate_copy(wrapped_input.begin(), mid, wrapped_input.end(), Write{output}); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == end(output)); + assert(equal(expected, output)); + } + { // Validate range overload + int output[5] = {-1, -1, -1, -1, -1}; + Read wrapped_input{input}; + iterator_t mid = next(wrapped_input.begin(), 3); + + auto result = rotate_copy(wrapped_input, mid, Write{output}); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_input.end()); + assert(result.out.peek() == end(output)); + assert(equal(expected, output)); + } + } +}; + +int main() { + STATIC_ASSERT((test_fwd_write(), true)); + test_fwd_write(); +} From 4e97255b3595a01f5268b98fad397e8579d14ed2 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Wed, 29 Jul 2020 21:14:13 -0700 Subject: [PATCH 25/50] Comparison category types become aggregates (#1065) ... for enregistration, to improve performance. Per a suggestion from Statementreply in the discussion at https://github.com/microsoft/STL/pull/1049#discussion_r456298672. --- stl/inc/compare | 63 ++++++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 43 deletions(-) diff --git a/stl/inc/compare b/stl/inc/compare index 717e7b4b789..b50e3518cc2 100644 --- a/stl/inc/compare +++ b/stl/inc/compare @@ -35,15 +35,7 @@ enum class _Compare_ord : _Compare_t { less = -1, greater = 1 }; enum class _Compare_ncmp : _Compare_t { unordered = -128 }; // CLASS partial_ordering -class partial_ordering { -public: - _NODISCARD constexpr explicit partial_ordering(const _Compare_eq _Value_) noexcept - : _Value(static_cast<_Compare_t>(_Value_)) {} - _NODISCARD constexpr explicit partial_ordering(const _Compare_ord _Value_) noexcept - : _Value(static_cast<_Compare_t>(_Value_)) {} - _NODISCARD constexpr explicit partial_ordering(const _Compare_ncmp _Value_) noexcept - : _Value(static_cast<_Compare_t>(_Value_)) {} - +struct partial_ordering { static const partial_ordering less; static const partial_ordering equivalent; static const partial_ordering greater; @@ -98,32 +90,25 @@ public: // The stored value is either less (0xff), equivalent (0x00), greater (0x01), or unordered (0x80). // Subtracting from 0 produces either 0x01, 0x00, 0xff, or 0x80. Note that the effect is to // exchange less for greater (and vice versa), while leaving equivalent and unordered unchanged. - return partial_ordering{static_cast<_Compare_ord>(0 - static_cast(_Val._Value))}; + return {static_cast<_Compare_t>(0 - static_cast(_Val._Value))}; } -private: _Compare_t _Value; }; -inline constexpr partial_ordering partial_ordering::less(_Compare_ord::less); -inline constexpr partial_ordering partial_ordering::equivalent(_Compare_eq::equivalent); -inline constexpr partial_ordering partial_ordering::greater(_Compare_ord::greater); -inline constexpr partial_ordering partial_ordering::unordered(_Compare_ncmp::unordered); +inline constexpr partial_ordering partial_ordering::less{static_cast<_Compare_t>(_Compare_ord::less)}; +inline constexpr partial_ordering partial_ordering::equivalent{static_cast<_Compare_t>(_Compare_eq::equivalent)}; +inline constexpr partial_ordering partial_ordering::greater{static_cast<_Compare_t>(_Compare_ord::greater)}; +inline constexpr partial_ordering partial_ordering::unordered{static_cast<_Compare_t>(_Compare_ncmp::unordered)}; // CLASS weak_ordering -class weak_ordering { -public: - _NODISCARD constexpr explicit weak_ordering(const _Compare_eq _Value_) noexcept - : _Value(static_cast<_Compare_t>(_Value_)) {} - _NODISCARD constexpr explicit weak_ordering(const _Compare_ord _Value_) noexcept - : _Value(static_cast<_Compare_t>(_Value_)) {} - +struct weak_ordering { static const weak_ordering less; static const weak_ordering equivalent; static const weak_ordering greater; constexpr operator partial_ordering() const noexcept { - return partial_ordering{static_cast<_Compare_ord>(_Value)}; + return {static_cast<_Compare_t>(_Value)}; } _NODISCARD friend constexpr bool operator==(const weak_ordering _Val, _Literal_zero) noexcept { @@ -169,36 +154,29 @@ public: } _NODISCARD friend constexpr weak_ordering operator<=>(_Literal_zero, const weak_ordering _Val) noexcept { - return weak_ordering{static_cast<_Compare_ord>(-_Val._Value)}; + return {static_cast<_Compare_t>(-_Val._Value)}; } -private: _Compare_t _Value; }; -inline constexpr weak_ordering weak_ordering::less(_Compare_ord::less); -inline constexpr weak_ordering weak_ordering::equivalent(_Compare_eq::equivalent); -inline constexpr weak_ordering weak_ordering::greater(_Compare_ord::greater); +inline constexpr weak_ordering weak_ordering::less{static_cast<_Compare_t>(_Compare_ord::less)}; +inline constexpr weak_ordering weak_ordering::equivalent{static_cast<_Compare_t>(_Compare_eq::equivalent)}; +inline constexpr weak_ordering weak_ordering::greater{static_cast<_Compare_t>(_Compare_ord::greater)}; // CLASS strong_ordering -class strong_ordering { -public: - _NODISCARD constexpr explicit strong_ordering(const _Compare_eq _Value_) noexcept - : _Value(static_cast<_Compare_t>(_Value_)) {} - _NODISCARD constexpr explicit strong_ordering(const _Compare_ord _Value_) noexcept - : _Value(static_cast<_Compare_t>(_Value_)) {} - +struct strong_ordering { static const strong_ordering less; static const strong_ordering equal; static const strong_ordering equivalent; static const strong_ordering greater; constexpr operator partial_ordering() const noexcept { - return partial_ordering{static_cast<_Compare_ord>(_Value)}; + return {static_cast<_Compare_t>(_Value)}; } constexpr operator weak_ordering() const noexcept { - return weak_ordering{static_cast<_Compare_ord>(_Value)}; + return {static_cast<_Compare_t>(_Value)}; } _NODISCARD friend constexpr bool operator==(const strong_ordering _Val, _Literal_zero) noexcept { @@ -244,17 +222,16 @@ public: } _NODISCARD friend constexpr strong_ordering operator<=>(_Literal_zero, const strong_ordering _Val) noexcept { - return strong_ordering{static_cast<_Compare_ord>(-_Val._Value)}; + return {static_cast<_Compare_t>(-_Val._Value)}; } -private: _Compare_t _Value; }; -inline constexpr strong_ordering strong_ordering::less(_Compare_ord::less); -inline constexpr strong_ordering strong_ordering::equal(_Compare_eq::equal); -inline constexpr strong_ordering strong_ordering::equivalent(_Compare_eq::equivalent); -inline constexpr strong_ordering strong_ordering::greater(_Compare_ord::greater); +inline constexpr strong_ordering strong_ordering::less{static_cast<_Compare_t>(_Compare_ord::less)}; +inline constexpr strong_ordering strong_ordering::equal{static_cast<_Compare_t>(_Compare_eq::equal)}; +inline constexpr strong_ordering strong_ordering::equivalent{static_cast<_Compare_t>(_Compare_eq::equivalent)}; +inline constexpr strong_ordering strong_ordering::greater{static_cast<_Compare_t>(_Compare_ord::greater)}; // FUNCTION is_eq _NODISCARD constexpr bool is_eq(const partial_ordering _Comp) noexcept { From 0381c25b10848ca1ff08606f0cde5ee8e2165049 Mon Sep 17 00:00:00 2001 From: ArtemSarmini <3sz3tt+git@gmail.com> Date: Sun, 2 Aug 2020 01:29:40 +0300 Subject: [PATCH 26/50] : is_function simplification (#460) Fixes #198. Co-authored-by: Casey Carter Co-authored-by: Stephan T. Lavavej --- stl/inc/functional | 4 +- stl/inc/type_traits | 267 +++++++++--------- .../test.cpp | 8 + 3 files changed, 147 insertions(+), 132 deletions(-) diff --git a/stl/inc/functional b/stl/inc/functional index 42f68a52603..05b969b5ef2 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -638,7 +638,7 @@ _NODISCARD const_mem_fun1_ref_t<_Result, _Ty, _Arg> mem_fun_ref(_Result (_Ty::*_ // FUNCTION TEMPLATE mem_fn template -class _Mem_fn : public _Weak_types<_Memptr>::type { +class _Mem_fn : public _Weak_types<_Memptr> { private: _Memptr _Pm; @@ -1449,7 +1449,7 @@ template struct _Binder_result_type { // provide result_type (sometimes) using _Decayed = decay_t<_Fx>; - using _All_weak_types = typename _Weak_types<_Decayed>::type; + using _All_weak_types = _Weak_types<_Decayed>; using type = conditional_t, _Weak_result_type<_All_weak_types>, _Forced_result_type<_Ret>>; diff --git a/stl/inc/type_traits b/stl/inc/type_traits index e17f1b7381c..41438975b0f 100644 --- a/stl/inc/type_traits +++ b/stl/inc/type_traits @@ -72,84 +72,6 @@ struct negation : bool_constant(_Trait::value)> {}; // The ne template _INLINE_VAR constexpr bool negation_v = negation<_Trait>::value; -// STRUCT TEMPLATE _Arg_types -template -struct _Arg_types {}; // provide argument_type, etc. (sometimes) - -template -struct _Arg_types<_Ty1> { - _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty1 _ARGUMENT_TYPE_NAME; -}; - -template -struct _Arg_types<_Ty1, _Ty2> { - _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty1 _FIRST_ARGUMENT_TYPE_NAME; - _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty2 _SECOND_ARGUMENT_TYPE_NAME; -}; - -// STRUCT TEMPLATE is_function -template -struct _Is_function { // determine whether _Ty is a function - using _Bool_type = false_type; // NB: members are user-visible via _Weak_types -}; - -#define _IS_FUNCTION(CALL_OPT, CV_OPT, REF_OPT, NOEXCEPT_OPT) \ - template \ - struct _Is_function<_Ret CALL_OPT(_Types...) CV_OPT REF_OPT NOEXCEPT_OPT> : _Arg_types<_Types...> { \ - using _Bool_type = true_type; \ - _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ret _RESULT_TYPE_NAME; \ - }; - -_NON_MEMBER_CALL_CV_REF_NOEXCEPT(_IS_FUNCTION) -#undef _IS_FUNCTION - -#define _IS_FUNCTION_ELLIPSIS(CV_REF_NOEXCEPT_OPT) \ - template \ - struct _Is_function<_Ret(_Types..., ...) CV_REF_NOEXCEPT_OPT> { /* no calling conventions for ellipsis */ \ - using _Bool_type = true_type; \ - _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ret _RESULT_TYPE_NAME; \ - }; - -_CLASS_DEFINE_CV_REF_NOEXCEPT(_IS_FUNCTION_ELLIPSIS) -#undef _IS_FUNCTION_ELLIPSIS - -template -struct is_function : _Is_function<_Ty>::_Bool_type {}; // determine whether _Ty is a function - -template -_INLINE_VAR constexpr bool is_function_v = _Is_function<_Ty>::_Bool_type::value; - -template -struct _Is_memfunptr { // base class for member function pointer predicates - using _Bool_type = false_type; // NB: members are user-visible via _Weak_types -}; - -#define _IS_MEMFUNPTR(CALL_OPT, CV_OPT, REF_OPT, NOEXCEPT_OPT) \ - template \ - struct _Is_memfunptr<_Ret (CALL_OPT _Arg0::*)(_Types...) CV_OPT REF_OPT NOEXCEPT_OPT> \ - : _Arg_types { \ - using _Bool_type = true_type; \ - _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ret _RESULT_TYPE_NAME; \ - using _Class_type = _Arg0; \ - using _Guide_type = enable_if, _Ret(_Types...)>; \ - }; - -_MEMBER_CALL_CV_REF_NOEXCEPT(_IS_MEMFUNPTR) -#undef _IS_MEMFUNPTR - -#define _IS_MEMFUNPTR_ELLIPSIS(CV_REF_NOEXCEPT_OPT) \ - template \ - struct _Is_memfunptr<_Ret (_Arg0::*)(_Types..., ...) \ - CV_REF_NOEXCEPT_OPT> { /* no calling conventions for ellipsis */ \ - using _Bool_type = true_type; \ - _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ret _RESULT_TYPE_NAME; \ - using _Class_type = _Arg0; \ - using _Guide_type = enable_if; \ - }; - -_CLASS_DEFINE_CV_REF_NOEXCEPT(_IS_MEMFUNPTR_ELLIPSIS) -#undef _IS_MEMFUNPTR_ELLIPSIS - // STRUCT TEMPLATE is_void template _INLINE_VAR constexpr bool is_void_v = is_same_v, void>; @@ -379,33 +301,6 @@ _INLINE_VAR constexpr bool is_reference_v<_Ty&&> = true; template struct is_reference : bool_constant> {}; -// STRUCT TEMPLATE is_member_object_pointer -template ::_Bool_type::value> -struct _Is_member_object_pointer { // determine whether _Ty is a pointer to member object - static constexpr bool value = false; -}; - -template -struct _Is_member_object_pointer<_Ty1 _Ty2::*, false> { - static constexpr bool value = true; - using _Class_type = _Ty2; -}; - -template -_INLINE_VAR constexpr bool is_member_object_pointer_v = // determine whether _Ty is a pointer to member object - _Is_member_object_pointer>::value; - -template -struct is_member_object_pointer : bool_constant> {}; - -// STRUCT TEMPLATE is_member_function_pointer -template -_INLINE_VAR constexpr bool is_member_function_pointer_v = // determine whether _Ty is a pointer to member function - _Is_memfunptr>::_Bool_type::value; - -template -struct is_member_function_pointer : bool_constant> {}; - // STRUCT TEMPLATE is_pointer template _INLINE_VAR constexpr bool is_pointer_v = false; // determine whether _Ty is a pointer @@ -454,13 +349,6 @@ _INLINE_VAR constexpr bool is_fundamental_v = is_arithmetic_v<_Ty> || is_void_v< template struct is_fundamental : bool_constant> {}; // determine whether _Ty is a fundamental type -// STRUCT TEMPLATE is_object -template -_INLINE_VAR constexpr bool is_object_v = !is_function_v<_Ty> && !is_reference_v<_Ty> && !is_void_v<_Ty>; - -template -struct is_object : bool_constant> {}; // determine whether _Ty is an object type - // STRUCT TEMPLATE is_convertible template struct is_convertible : bool_constant<__is_convertible_to(_From, _To)> { @@ -484,20 +372,63 @@ struct is_compound : bool_constant> {}; // determine whet template _INLINE_VAR constexpr bool is_compound_v = !is_fundamental_v<_Ty>; -// STRUCT TEMPLATE is_member_pointer -template -_INLINE_VAR constexpr bool is_member_pointer_v = is_member_object_pointer_v<_Ty> || is_member_function_pointer_v<_Ty>; +// STRUCT TEMPLATE _Arg_types +template +struct _Arg_types {}; // provide argument_type, etc. when sizeof...(_Types) is 1 or 2 + +template +struct _Arg_types<_Ty1> { + _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty1 _ARGUMENT_TYPE_NAME; +}; + +template +struct _Arg_types<_Ty1, _Ty2> { + _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty1 _FIRST_ARGUMENT_TYPE_NAME; + _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty2 _SECOND_ARGUMENT_TYPE_NAME; +}; +// STRUCT TEMPLATE is_member_function_pointer template -struct is_member_pointer : bool_constant> {}; // determine whether _Ty is a pointer to member +struct _Is_memfunptr { // base class for member function pointer predicates + using _Bool_type = false_type; // NB: members are user-visible via _Weak_types +}; -// STRUCT TEMPLATE is_scalar +#define _IS_MEMFUNPTR(CALL_OPT, CV_OPT, REF_OPT, NOEXCEPT_OPT) \ + template \ + struct _Is_memfunptr<_Ret (CALL_OPT _Arg0::*)(_Types...) CV_OPT REF_OPT NOEXCEPT_OPT> \ + : _Arg_types { \ + using _Bool_type = true_type; \ + _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ret _RESULT_TYPE_NAME; \ + using _Class_type = _Arg0; \ + using _Guide_type = enable_if, _Ret(_Types...)>; \ + }; + +_MEMBER_CALL_CV_REF_NOEXCEPT(_IS_MEMFUNPTR) +#undef _IS_MEMFUNPTR + +#define _IS_MEMFUNPTR_ELLIPSIS(CV_REF_NOEXCEPT_OPT) \ + template \ + struct _Is_memfunptr<_Ret (_Arg0::*)(_Types..., ...) \ + CV_REF_NOEXCEPT_OPT> { /* no calling conventions for ellipsis */ \ + using _Bool_type = true_type; \ + _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ret _RESULT_TYPE_NAME; \ + using _Class_type = _Arg0; \ + using _Guide_type = enable_if; \ + }; + +_CLASS_DEFINE_CV_REF_NOEXCEPT(_IS_MEMFUNPTR_ELLIPSIS) +#undef _IS_MEMFUNPTR_ELLIPSIS + +#ifdef __clang__ template -_INLINE_VAR constexpr bool is_scalar_v = // determine whether _Ty is a scalar type - is_arithmetic_v<_Ty> || is_enum_v<_Ty> || is_pointer_v<_Ty> || is_member_pointer_v<_Ty> || is_null_pointer_v<_Ty>; +_INLINE_VAR constexpr bool is_member_function_pointer_v = __is_member_function_pointer(_Ty); +#else // ^^^ Clang ^^^ / vvv Other vvv +template +_INLINE_VAR constexpr bool is_member_function_pointer_v = _Is_memfunptr>::_Bool_type::value; +#endif // ^^^ Other ^^^ template -struct is_scalar : bool_constant> {}; +struct is_member_function_pointer : bool_constant> {}; // STRUCT TEMPLATE is_const template @@ -519,6 +450,65 @@ _INLINE_VAR constexpr bool is_volatile_v = true; template struct is_volatile : bool_constant> {}; +// STRUCT TEMPLATE is_function +template +_INLINE_VAR constexpr bool is_function_v = // only function types and reference types can't be const qualified + !is_const_v && !is_reference_v<_Ty>; + +template +struct is_function : bool_constant> {}; + +// STRUCT TEMPLATE is_object +template +_INLINE_VAR constexpr bool is_object_v = // only function types and reference types can't be const qualified + is_const_v && !is_void_v<_Ty>; + +template +struct is_object : bool_constant> {}; + +// STRUCT TEMPLATE is_member_object_pointer +template +struct _Is_member_object_pointer { + static constexpr bool value = false; +}; + +template +struct _Is_member_object_pointer<_Ty1 _Ty2::*> { + static constexpr bool value = !is_function_v<_Ty1>; + using _Class_type = _Ty2; +}; + +#ifdef __clang__ +template +_INLINE_VAR constexpr bool is_member_object_pointer_v = __is_member_object_pointer(_Ty); +#else // ^^^ Clang / Other vvv +template +_INLINE_VAR constexpr bool is_member_object_pointer_v = _Is_member_object_pointer>::value; +#endif // ^^^ Other ^^^ + +template +struct is_member_object_pointer : bool_constant> {}; + +// STRUCT TEMPLATE is_member_pointer +#ifdef __clang__ +template +_INLINE_VAR constexpr bool is_member_pointer_v = __is_member_pointer(_Ty); +#else // ^^^ Clang / Other vvv +template +_INLINE_VAR constexpr bool is_member_pointer_v = is_member_object_pointer_v<_Ty> || is_member_function_pointer_v<_Ty>; +#endif // ^^^ Other ^^^ + +template +struct is_member_pointer : bool_constant> {}; // determine whether _Ty is a pointer to member + +// STRUCT TEMPLATE is_scalar +template +_INLINE_VAR constexpr bool is_scalar_v = // determine whether _Ty is a scalar type + is_arithmetic_v<_Ty> || is_enum_v<_Ty> || is_pointer_v<_Ty> || is_member_pointer_v<_Ty> || is_null_pointer_v<_Ty>; + +template +struct is_scalar : bool_constant> {}; + // STRUCT TEMPLATE is_pod template struct _CXX20_DEPRECATE_IS_POD is_pod : bool_constant<__is_pod(_Ty)> {}; // determine whether _Ty is a POD type @@ -1810,7 +1800,28 @@ inline constexpr bool is_nothrow_invocable_r_v = _Select_invoke_traits<_Callable, _Args...>::template _Is_nothrow_invocable_r<_Rx>::value; #endif // _HAS_CXX17 -// STRUCT TEMPLATE _Weak_types +// ALIAS TEMPLATE _Weak_types +template +struct _Function_args {}; // determine whether _Ty is a function + +#define _FUNCTION_ARGS(CALL_OPT, CV_OPT, REF_OPT, NOEXCEPT_OPT) \ + template \ + struct _Function_args<_Ret CALL_OPT(_Types...) CV_OPT REF_OPT NOEXCEPT_OPT> : _Arg_types<_Types...> { \ + _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ret _RESULT_TYPE_NAME; \ + }; + +_NON_MEMBER_CALL_CV_REF_NOEXCEPT(_FUNCTION_ARGS) +#undef _FUNCTION_ARGS + +#define _FUNCTION_ARGS_ELLIPSIS(CV_REF_NOEXCEPT_OPT) \ + template \ + struct _Function_args<_Ret(_Types..., ...) CV_REF_NOEXCEPT_OPT> { /* no calling conventions for ellipsis */ \ + _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ret _RESULT_TYPE_NAME; \ + }; + +_CLASS_DEFINE_CV_REF_NOEXCEPT(_FUNCTION_ARGS_ELLIPSIS) +#undef _FUNCTION_ARGS_ELLIPSIS + template struct _Weak_result_type {}; // default definition @@ -1826,8 +1837,8 @@ struct _Weak_argument_type : _Weak_result_type<_Ty> {}; // default definition _STL_DISABLE_DEPRECATED_WARNING template -struct _Weak_argument_type<_Ty, void_t> - : _Weak_result_type<_Ty> { // defined if _Ty::argument_type exists +struct _Weak_argument_type<_Ty, void_t> : _Weak_result_type<_Ty> { + // defined if _Ty::argument_type exists _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef typename _Ty::argument_type _ARGUMENT_TYPE_NAME; }; _STL_RESTORE_DEPRECATED_WARNING @@ -1846,12 +1857,8 @@ struct _Weak_binary_args<_Ty, void_t -struct _Weak_types { // provide nested types (sometimes) - using _Is_f_or_pf = _Is_function>; - using _Is_pmf = _Is_memfunptr>; - using type = conditional_t>, _Is_f_or_pf, - conditional_t, _Is_pmf, _Weak_binary_args<_Ty>>>; -}; +using _Weak_types = conditional_t>, _Function_args>, + conditional_t, _Is_memfunptr>, _Weak_binary_args<_Ty>>>; // CLASS TEMPLATE reference_wrapper template @@ -1868,7 +1875,7 @@ struct _Refwrap_has_ctor_from<_Ty, _Uty, void_t( template class reference_wrapper #if !_HAS_CXX20 - : public _Weak_types<_Ty>::type + : public _Weak_types<_Ty> #endif // !_HAS_CXX20 { public: diff --git a/tests/std/tests/Dev09_158457_tr1_mem_fn_calling_conventions/test.cpp b/tests/std/tests/Dev09_158457_tr1_mem_fn_calling_conventions/test.cpp index c303105ce08..1c2aa9b9537 100644 --- a/tests/std/tests/Dev09_158457_tr1_mem_fn_calling_conventions/test.cpp +++ b/tests/std/tests/Dev09_158457_tr1_mem_fn_calling_conventions/test.cpp @@ -168,6 +168,14 @@ STATIC_ASSERT(is_member_function_pointer_v); STATIC_ASSERT(is_member_function_pointer_v); STATIC_ASSERT(is_member_function_pointer_v); +STATIC_ASSERT(is_member_pointer_v); +STATIC_ASSERT(is_member_pointer_v); +STATIC_ASSERT(is_member_pointer_v); +STATIC_ASSERT(is_member_pointer_v); +STATIC_ASSERT(is_member_pointer_v); +STATIC_ASSERT(is_member_pointer_v); +STATIC_ASSERT(is_member_pointer_v); + STATIC_ASSERT(is_function_v); STATIC_ASSERT(is_function_v); STATIC_ASSERT(is_function_v); From 1ff3d1ef115a983b655bd721c998337a9af550ad Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sun, 2 Aug 2020 00:58:39 +0200 Subject: [PATCH 27/50] Optimize fill and fill_n for vector (#879) Co-authored-by: Billy Robert O'Neal III Co-authored-by: Stephan T. Lavavej --- stl/inc/vector | 59 ++++++++++ stl/inc/xutility | 61 ++++++----- tests/std/test.lst | 1 + .../env.lst | 4 + .../test.cpp | 101 ++++++++++++++++++ 5 files changed, 202 insertions(+), 24 deletions(-) create mode 100644 tests/std/tests/GH_000625_vector_bool_optimization/env.lst create mode 100644 tests/std/tests/GH_000625_vector_bool_optimization/test.cpp diff --git a/stl/inc/vector b/stl/inc/vector index 17902275c58..5a18de75a39 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -2882,6 +2882,65 @@ namespace pmr { using vector = _STD vector<_Ty, polymorphic_allocator<_Ty>>; } // namespace pmr #endif // _HAS_CXX17 + +#if _HAS_IF_CONSTEXPR +// VARIABLE TEMPLATE _Is_vb_iterator +template +_INLINE_VAR constexpr bool _Is_vb_iterator<_Vb_iterator<_Alloc>, _RequiresMutable> = true; + +template +_INLINE_VAR constexpr bool _Is_vb_iterator<_Vb_const_iterator<_Alloc>, false> = true; + +template +_CONSTEXPR20 void _Fill_vbool(_FwdIt _First, _FwdIt _Last, const _Ty& _Val) { + // Set [_First, _Last) to _Val + if (_First == _Last) { + return; + } + + _Vbase* _VbFirst = const_cast<_Vbase*>(_First._Myptr); + _Vbase* const _VbLast = const_cast<_Vbase*>(_Last._Myptr); + + const auto _FirstSourceMask = static_cast<_Vbase>(-1) << _First._Myoff; + const auto _FirstDestMask = ~_FirstSourceMask; + const auto _FillVal = static_cast<_Vbase>(_Val ? -1 : 0); + + if (_VbFirst == _VbLast) { + // We already excluded _First == _Last, so here _Last._Myoff > 0 and the shift is safe + const auto _LastSourceMask = static_cast<_Vbase>(-1) >> (_VBITS - _Last._Myoff); + const auto _LastDestMask = ~_LastSourceMask; + const auto _SourceMask = _FirstSourceMask & _LastSourceMask; + const auto _DestMask = _FirstDestMask | _LastDestMask; + *_VbFirst = (*_VbFirst & _DestMask) | (_FillVal & _SourceMask); + return; + } + + *_VbFirst = (*_VbFirst & _FirstDestMask) | (_FillVal & _FirstSourceMask); + ++_VbFirst; + +#ifdef __cpp_lib_is_constant_evaluated + if (_STD is_constant_evaluated()) { + for (; _VbFirst != _VbLast; ++_VbFirst) { + *_VbFirst = _FillVal; + } + } else +#endif // __cpp_lib_is_constant_evaluated + { + const auto _VbFirst_ch = reinterpret_cast(_VbFirst); + const auto _VbLast_ch = reinterpret_cast(_VbLast); + const auto _Count_ch = static_cast(_VbLast_ch - _VbFirst_ch); + const auto _ValChar = static_cast(_Val ? -1 : 0); + _CSTD memset(_VbFirst, _ValChar, _Count_ch); + _VbFirst = _VbLast; + } + + if (_Last._Myoff != 0) { + const auto _LastSourceMask = static_cast<_Vbase>(-1) >> (_VBITS - _Last._Myoff); + const auto _LastDestMask = ~_LastSourceMask; + *_VbFirst = (*_VbFirst & _LastDestMask) | (_FillVal & _LastSourceMask); + } +} +#endif // _HAS_IF_CONSTEXPR _STD_END #pragma pop_macro("new") diff --git a/stl/inc/xutility b/stl/inc/xutility index b22d75d57ef..0bebdd19d2b 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -4302,6 +4302,10 @@ _OutIt _Copy_memmove(move_iterator<_InIt> _First, move_iterator<_InIt> _Last, _O } #if _HAS_IF_CONSTEXPR +// VARIABLE TEMPLATE _Is_vb_iterator +template +_INLINE_VAR constexpr bool _Is_vb_iterator = false; + template _CONSTEXPR20 _OutIt _Copy_unchecked(_InIt _First, _InIt _Last, _OutIt _Dest) { // copy [_First, _Last) to [_Dest, ...) @@ -4717,20 +4721,24 @@ template _CONSTEXPR20 void fill(const _FwdIt _First, const _FwdIt _Last, const _Ty& _Val) { // copy _Val through [_First, _Last) _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(_First); - const auto _ULast = _Get_unwrapped(_Last); - if constexpr (_Fill_memset_is_safe) { + if constexpr (_Is_vb_iterator<_FwdIt, true>) { + _Fill_vbool(_First, _Last, _Val); + } else { + auto _UFirst = _Get_unwrapped(_First); + const auto _ULast = _Get_unwrapped(_Last); + if constexpr (_Fill_memset_is_safe) { #ifdef __cpp_lib_is_constant_evaluated - if (!_STD is_constant_evaluated()) + if (!_STD is_constant_evaluated()) #endif // __cpp_lib_is_constant_evaluated - { - _CSTD memset(_UFirst, static_cast(_Val), static_cast(_ULast - _UFirst)); - return; + { + _CSTD memset(_UFirst, static_cast(_Val), static_cast(_ULast - _UFirst)); + return; + } } - } - for (; _UFirst != _ULast; ++_UFirst) { - *_UFirst = _Val; + for (; _UFirst != _ULast; ++_UFirst) { + *_UFirst = _Val; + } } } #else // ^^^ _HAS_IF_CONSTEXPR // !_HAS_IF_CONSTEXPR vvv @@ -4773,26 +4781,31 @@ _CONSTEXPR20 _OutIt fill_n(_OutIt _Dest, const _Diff _Count_raw, const _Ty& _Val // copy _Val _Count times through [_Dest, ...) _Algorithm_int_t<_Diff> _Count = _Count_raw; if (0 < _Count) { - auto _UDest = _Get_unwrapped_n(_Dest, _Count); - if constexpr (_Fill_memset_is_safe) { + if constexpr (_Is_vb_iterator<_OutIt, true>) { + const auto _Last = _Dest + static_cast(_Count); + _Fill_vbool(_Dest, _Last, _Val); + return _Last; + } else { + auto _UDest = _Get_unwrapped_n(_Dest, _Count); + if constexpr (_Fill_memset_is_safe) { #ifdef __cpp_lib_is_constant_evaluated - if (!_STD is_constant_evaluated()) + if (!_STD is_constant_evaluated()) #endif // __cpp_lib_is_constant_evaluated - { - _CSTD memset(_UDest, static_cast(_Val), static_cast(_Count)); - _UDest += _Count; - _Seek_wrapped(_Dest, _UDest); - return _Dest; + { + _CSTD memset(_UDest, static_cast(_Val), static_cast(_Count)); + _UDest += _Count; + _Seek_wrapped(_Dest, _UDest); + return _Dest; + } } - } - for (; 0 < _Count; --_Count, (void) ++_UDest) { - *_UDest = _Val; - } + for (; 0 < _Count; --_Count, (void) ++_UDest) { + *_UDest = _Val; + } - _Seek_wrapped(_Dest, _UDest); + _Seek_wrapped(_Dest, _UDest); + } } - return _Dest; } diff --git a/tests/std/test.lst b/tests/std/test.lst index 04b21077e19..d639d5e2c81 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -157,6 +157,7 @@ tests\Dev11_1158803_regex_thread_safety tests\Dev11_1180290_filesystem_error_code tests\GH_000457_system_error_message tests\GH_000545_include_compare +tests\GH_000625_vector_bool_optimization tests\GH_000685_condition_variable_any tests\GH_000690_overaligned_function tests\GH_000890_pow_template diff --git a/tests/std/tests/GH_000625_vector_bool_optimization/env.lst b/tests/std/tests/GH_000625_vector_bool_optimization/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_000625_vector_bool_optimization/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_000625_vector_bool_optimization/test.cpp b/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp new file mode 100644 index 00000000000..3f26088c87b --- /dev/null +++ b/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +using namespace std; + +constexpr int blockSize = 32; +static_assert(blockSize == _VBITS, "Invalid block size"); + +void test_fill_helper(const size_t length) { + // No offset + { + vector result_true(length, true); + result_true.resize(length + 3, false); + vector dest_true(length + 3, false); + fill(dest_true.begin(), prev(dest_true.end(), 3), true); + assert(dest_true == result_true); + + vector result_false(length, false); + result_false.resize(length + 3, true); + vector dest_false(length + 3, true); + fill(dest_false.begin(), prev(dest_false.end(), 3), false); + assert(dest_false == result_false); + + vector result_true_n(length, true); + result_true_n.resize(length + 3, false); + vector dest_true_n(length + 3, false); + const auto res_fill_n = fill_n(dest_true_n.begin(), length, true); + assert(dest_true_n == result_true_n); + assert(res_fill_n == prev(dest_true_n.end(), 3)); + + vector result_false_n(length, false); + result_false_n.resize(length + 3, true); + vector dest_false_n(length + 3, true); + fill_n(dest_false_n.begin(), length, false); + assert(dest_false_n == result_false_n); + } + + // With offset + { + vector result_true(length, true); + result_true.resize(length + 3, false); + result_true.insert(result_true.begin(), false); + vector dest_true(length + 4, false); + fill(next(dest_true.begin()), prev(dest_true.end(), 3), true); + assert(dest_true == result_true); + + vector result_false(length, false); + result_false.resize(length + 3, true); + result_false.insert(result_false.begin(), true); + vector dest_false(length + 4, true); + fill(next(dest_false.begin()), prev(dest_false.end(), 3), false); + assert(dest_false == result_false); + + vector result_true_n(length, true); + result_true_n.resize(length + 3, false); + result_true_n.insert(result_true_n.begin(), false); + vector dest_true_n(length + 4, false); + const auto res_fill_n = fill_n(next(dest_true_n.begin()), length, true); + assert(dest_true_n == result_true_n); + assert(res_fill_n == prev(dest_true_n.end(), 3)); + + vector result_false_n(length, false); + result_false_n.resize(length + 3, true); + result_false_n.insert(result_false_n.begin(), true); + vector dest_false_n(length + 4, true); + fill_n(next(dest_false_n.begin()), length, false); + assert(dest_false_n == result_false_n); + } +} + +bool test_fill() { + // Empty + test_fill_helper(0); + + // One block, ends within block + test_fill_helper(15); + + // One block, ends at block boundary + test_fill_helper(blockSize); + + // Multiple blocks, no memset, within block + test_fill_helper(blockSize + 11); + + // Multiple blocks, no memset, ends at block boundary + test_fill_helper(2 * blockSize); + + // Multiple blocks, with memset, within block + test_fill_helper(3 * blockSize + 5); + + // Multiple blocks, with memset, ends at block boundary + test_fill_helper(4 * blockSize); + return true; +} + +int main() { + test_fill(); +} From accaf18978a6181d1057853fa5de6da9f31e5ebb Mon Sep 17 00:00:00 2001 From: Michael Schellenberger Costa Date: Sun, 2 Aug 2020 01:03:08 +0200 Subject: [PATCH 28/50] Modernize ranges::swap_ranges and ranges::distance (#1062) Co-authored-by: Casey Carter Co-authored-by: Stephan T. Lavavej --- stl/inc/algorithm | 41 ++++-- stl/inc/xutility | 29 ++-- .../P0896R4_ranges_alg_includes/test.cpp | 2 +- .../P0896R4_ranges_alg_swap_ranges/test.cpp | 132 ++++++++++++------ 4 files changed, 136 insertions(+), 68 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index ed4466cfcd8..e625cdb5326 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -3402,16 +3402,13 @@ namespace ranges { _It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2) const { _Adl_verify_range(_First1, _Last1); _Adl_verify_range(_First2, _Last2); - auto _UFirst1 = _Get_unwrapped(_STD move(_First1)); - auto _ULast1 = _Get_unwrapped(_STD move(_Last1)); - auto _UFirst2 = _Get_unwrapped(_STD move(_First2)); - auto _ULast2 = _Get_unwrapped(_STD move(_Last2)); - for (; _UFirst1 != _ULast1 && _UFirst2 != _ULast2; ++_UFirst1, (void) ++_UFirst2) { - _RANGES iter_swap(_UFirst1, _UFirst2); - } - _Seek_wrapped(_First1, _STD move(_UFirst1)); - _Seek_wrapped(_First2, _STD move(_UFirst2)); + auto _UResult = + _Swap_ranges_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.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); return {_STD move(_First1), _STD move(_First2)}; } @@ -3419,9 +3416,33 @@ namespace ranges { requires indirectly_swappable, iterator_t<_Rng2>> constexpr swap_ranges_result, borrowed_iterator_t<_Rng2>> operator()( _Rng1&& _Range1, _Rng2&& _Range2) const { - return (*this)(_RANGES begin(_Range1), _RANGES end(_Range1), _RANGES begin(_Range2), _RANGES end(_Range2)); + auto _First1 = _RANGES begin(_Range1); + auto _First2 = _RANGES begin(_Range2); + + auto _UResult = _Swap_ranges_unchecked( + _Get_unwrapped(_STD move(_First1)), _Uend(_Range1), _Get_unwrapped(_STD move(_First2)), _Uend(_Range2)); + + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + return {_STD move(_First1), _STD move(_First2)}; } // clang-format on + private: + template + _NODISCARD static constexpr swap_ranges_result<_It1, _It2> _Swap_ranges_unchecked( + _It1 _First1, const _Se1 _Last1, _It2 _First2, const _Se2 _Last2) { + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_swappable<_It1, _It2>); + + for (; _First1 != _Last1 && _First2 != _Last2; ++_First1, (void) ++_First2) { + _RANGES iter_swap(_First1, _First2); + } + + return {_STD move(_First1), _STD move(_First2)}; + } }; inline constexpr _Swap_ranges_fn swap_ranges{_Not_quite_object::_Construct_tag{}}; diff --git a/stl/inc/xutility b/stl/inc/xutility index 0bebdd19d2b..cc9cf7e15e5 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -3243,25 +3243,32 @@ namespace ranges { return _Last - _First; } else { _Adl_verify_range(_First, _Last); - auto _UFirst = _Get_unwrapped(static_cast<_It&&>(_First)); - const auto _ULast = _Get_unwrapped(static_cast<_Se&&>(_Last)); - iter_difference_t<_It> _Count = 0; - for (; _UFirst != _ULast; ++_UFirst) { - ++_Count; - } - - return _Count; + return _Distance_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last))); } } template - _NODISCARD constexpr range_difference_t<_Rng> operator()(_Rng&& _Val) const { + _NODISCARD constexpr range_difference_t<_Rng> operator()(_Rng&& _Range) const { if constexpr (sized_range<_Rng>) { - return static_cast>(_RANGES size(_Val)); + return static_cast>(_RANGES size(_Range)); } else { - return (*this)(_RANGES begin(_Val), _RANGES end(_Val)); + return _Distance_unchecked(_Ubegin(_Range), _Uend(_Range)); } } + + private: + template + _NODISCARD static constexpr iter_difference_t<_It> _Distance_unchecked(_It _First, const _Se _Last) { + _STL_INTERNAL_STATIC_ASSERT(input_or_output_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + + iter_difference_t<_It> _Count = 0; + for (; _First != _Last; ++_First) { + ++_Count; + } + + return _Count; + } }; // VARIABLE ranges::distance diff --git a/tests/std/tests/P0896R4_ranges_alg_includes/test.cpp b/tests/std/tests/P0896R4_ranges_alg_includes/test.cpp index 082ca91f38e..54929f26822 100644 --- a/tests/std/tests/P0896R4_ranges_alg_includes/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_includes/test.cpp @@ -68,7 +68,7 @@ int main() { #else // ^^^ test all permutations of range properties / test only interesting permutations vvv template using test_range = test::range}, IsProxyRef>; + test::CanCompare{derived_from}, IsProxyRef>; constexpr void run_tests() { using namespace test; diff --git a/tests/std/tests/P0896R4_ranges_alg_swap_ranges/test.cpp b/tests/std/tests/P0896R4_ranges_alg_swap_ranges/test.cpp index b30d2c6466b..534280d29d1 100644 --- a/tests/std/tests/P0896R4_ranges_alg_swap_ranges/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_swap_ranges/test.cpp @@ -2,65 +2,105 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include -#include #include #include #include #include #include +using namespace std; -constexpr void smoke_test() { - using ranges::swap_ranges, ranges::swap_ranges_result, ranges::iterator_t; - using std::same_as; - using I1 = iterator_t>; +// Validate that swap_ranges_result aliases in_in_result +STATIC_ASSERT(same_as, ranges::in_in_result>); - // Validate that swap_ranges_result aliases in_in_result - STATIC_ASSERT(same_as, ranges::in_in_result>); +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{})), + ranges::swap_ranges_result>); +STATIC_ASSERT(same_as{}, borrowed{})), + ranges::swap_ranges_result>); +STATIC_ASSERT(same_as{}, borrowed{})), + ranges::swap_ranges_result>); +STATIC_ASSERT( + same_as{}, borrowed{})), ranges::swap_ranges_result>); - { // Validate ranges overload - using R = std::array; - R range1 = {13, 53, 1876}; - R range2 = {34, 243, 9366}; - int const expected_output1[] = {34, 243, 9366}; - int const expected_output2[] = {13, 53, 1876}; - auto result = swap_ranges(basic_borrowed_range{range1}, basic_borrowed_range{range2}); - STATIC_ASSERT(same_as>); - assert(result.in1 == basic_borrowed_range{range1}.end()); - assert(result.in2 == basic_borrowed_range{range2}.end()); - assert(ranges::equal(range1, expected_output1)); - assert(ranges::equal(range2, expected_output2)); - } - { // Validate iterator + sentinel overload - int range1[] = {13, 53, 1876}; - int range2[] = {34, 243, 9366}; - basic_borrowed_range wrapped_range1{range1}; - basic_borrowed_range wrapped_range2{range2}; - int const expected_output1[] = {34, 243, 9366}; - int const expected_output2[] = {13, 53, 1876}; - auto result = - swap_ranges(wrapped_range1.begin(), wrapped_range1.end(), wrapped_range2.begin(), wrapped_range2.end()); - STATIC_ASSERT(same_as>); - assert(result.in1 == wrapped_range1.end()); - assert(result.in2 == wrapped_range2.end()); - assert(ranges::equal(range1, expected_output1)); - assert(ranges::equal(range2, expected_output2)); +struct instantiator { + static constexpr int expected_output1[3] = {34, 243, 9366}; + static constexpr int expected_output2[3] = {13, 53, 1876}; + + template + static constexpr void call() { + using ranges::swap_ranges, ranges::swap_ranges_result, ranges::iterator_t, ranges::equal; + { // Validate iterator + sentinel overload + int input1[3] = {13, 53, 1876}; + int input2[3] = {34, 243, 9366}; + ReadWrite1 wrapped_input1{input1}; + ReadWrite2 wrapped_input2{input2}; + + auto result = + swap_ranges(wrapped_input1.begin(), wrapped_input1.end(), wrapped_input2.begin(), wrapped_input2.end()); + STATIC_ASSERT( + same_as, iterator_t>>); + assert(result.in1 == wrapped_input1.end()); + assert(result.in2 == wrapped_input2.end()); + assert(equal(input1, expected_output1)); + assert(equal(input2, expected_output2)); + } + { // Validate range overload + int input1[3] = {13, 53, 1876}; + int input2[3] = {34, 243, 9366}; + ReadWrite1 wrapped_input1{input1}; + ReadWrite2 wrapped_input2{input2}; + + auto result = swap_ranges(wrapped_input1, wrapped_input2); + STATIC_ASSERT( + same_as, iterator_t>>); + assert(result.in1 == wrapped_input1.end()); + assert(result.in2 == wrapped_input2.end()); + assert(equal(input1, expected_output1)); + assert(equal(input2, expected_output2)); + } } +}; + +#ifdef TEST_EVERYTHING +int main() { + // no constexpr tests here; the below exceeds constexpr step limits + test_in_in(); +} +#else // ^^^ test all permutations of range properties / test only interesting permutations vvv +template +using test_range = test::range}, IsProxyRef>; + +constexpr void run_tests() { + using namespace test; + // The algorithm is completely oblivious to: + // * categories stronger than input + // * whether the end sentinel is an iterator + // * size information + // * iterator and/or sentinel differencing + // so let's vary proxyness for coverage and add a range of each category out of paranoia. + + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); + instantiator::call, test_range>(); } int main() { #if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-938163 - STATIC_ASSERT((smoke_test(), true)); + STATIC_ASSERT((run_tests(), true)); #endif // TRANSITION, VSO-938163 - smoke_test(); + run_tests(); } - -struct instantiator { - template - static void call(In1 in1 = {}, In2 in2 = {}) { - (void) ranges::swap_ranges(in1, in2); - (void) ranges::swap_ranges(ranges::begin(in1), ranges::end(in1), ranges::begin(in2), ranges::end(in2)); - } -}; - -template void test_in_in(); +#endif // TEST_EVERYTHING From e58a94505b1193ee67971365a9125e1f4aaa8be9 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Sat, 1 Aug 2020 16:08:38 -0700 Subject: [PATCH 29/50] Implement ranges::nth_element (#1063) --- stl/inc/algorithm | 246 +++++++++++++++++- tests/std/test.lst | 1 + .../P0896R4_ranges_alg_nth_element/env.lst | 4 + .../P0896R4_ranges_alg_nth_element/test.cpp | 82 ++++++ 4 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_nth_element/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_nth_element/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index e625cdb5326..4e431bb53a0 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -7268,22 +7268,22 @@ void inplace_merge(_ExPo&&, _BidIt _First, _BidIt _Mid, _BidIt _Last) noexcept / // FUNCTION TEMPLATE sort template -_CONSTEXPR20 _BidIt _Insertion_sort_unchecked(_BidIt _First, const _BidIt _Last, _Pr _Pred) { +_CONSTEXPR20 _BidIt _Insertion_sort_unchecked(const _BidIt _First, const _BidIt _Last, _Pr _Pred) { // insertion sort [_First, _Last) if (_First != _Last) { - for (_BidIt _Next = _First; ++_Next != _Last;) { // order next element - _BidIt _Next1 = _Next; - _Iter_value_t<_BidIt> _Val = _STD move(*_Next); + for (_BidIt _Mid = _First; ++_Mid != _Last;) { // order next element + _BidIt _Hole = _Mid; + _Iter_value_t<_BidIt> _Val = _STD move(*_Mid); if (_DEBUG_LT_PRED(_Pred, _Val, *_First)) { // found new earliest element, move to front - _Move_backward_unchecked(_First, _Next, ++_Next1); + _Move_backward_unchecked(_First, _Mid, ++_Hole); *_First = _STD move(_Val); } else { // look for insertion point after first - for (_BidIt _First1 = _Next1; _DEBUG_LT_PRED(_Pred, _Val, *--_First1); _Next1 = _First1) { - *_Next1 = _STD move(*_First1); // move hole down + for (_BidIt _Prev = _Hole; _DEBUG_LT_PRED(_Pred, _Val, *--_Prev); _Hole = _Prev) { + *_Hole = _STD move(*_Prev); // move hole down } - *_Next1 = _STD move(_Val); // insert element in hole + *_Hole = _STD move(_Val); // insert element in hole } } } @@ -7347,6 +7347,7 @@ _CONSTEXPR20 pair<_RanIt, _RanIt> _Partition_by_median_guess_unchecked(_RanIt _F for (;;) { // partition for (; _Gfirst < _Last; ++_Gfirst) { if (_DEBUG_LT_PRED(_Pred, *_Pfirst, *_Gfirst)) { + continue; } else if (_Pred(*_Gfirst, *_Pfirst)) { break; } else if (_Plast != _Gfirst) { @@ -7359,6 +7360,7 @@ _CONSTEXPR20 pair<_RanIt, _RanIt> _Partition_by_median_guess_unchecked(_RanIt _F for (; _First < _Glast; --_Glast) { if (_DEBUG_LT_PRED(_Pred, *_Prev_iter(_Glast), *_Pfirst)) { + continue; } else if (_Pred(*_Pfirst, *_Prev_iter(_Glast))) { break; } else if (--_Pfirst != _Prev_iter(_Glast)) { @@ -7444,6 +7446,156 @@ void sort(_ExPo&& _Exec, const _RanIt _First, const _RanIt _Last) noexcept /* te // order [_First, _Last) _STD sort(_STD forward<_ExPo>(_Exec), _First, _Last, less{}); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // clang-format off + template + requires sortable<_It, _Pr, _Pj> + constexpr void _Insertion_sort_common(const _It _First, const _It _Last, _Pr _Pred, _Pj _Proj) { + // insertion sort [_First, _Last) + + if (_First == _Last) { // empty range is sorted + return; + } + + for (auto _Mid = _First; ++_Mid != _Last;) { // order next element + iter_value_t<_It> _Val = _RANGES iter_move(_Mid); + auto _Hole = _Mid; + + for (auto _Prev = _Hole;;) { + --_Prev; + if (!_STD invoke(_Pred, _STD invoke(_Proj, _Val), _STD invoke(_Proj, *_Prev))) { + break; + } + *_Hole = _RANGES iter_move(_Prev); // move hole down + if (--_Hole == _First) { + break; + } + } + + *_Hole = _STD move(_Val); // insert element in hole + } + } + + template + requires sortable<_It, _Pr, _Pj> + constexpr void _Med3_unchecked(_It _First, _It _Mid, _It _Last, _Pr _Pred, _Pj _Proj) { + // sort median of three elements to middle + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Mid), _STD invoke(_Proj, *_First))) { + _RANGES iter_swap(_Mid, _First); + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_Last), _STD invoke(_Proj, *_Mid))) { + return; + } + + // swap middle and last, then test first again + _RANGES iter_swap(_Last, _Mid); + + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Mid), _STD invoke(_Proj, *_First))) { + _RANGES iter_swap(_Mid, _First); + } + } + + template + requires sortable<_It, _Pr, _Pj> + constexpr void _Guess_median_unchecked(_It _First, _It _Mid, _It _Last, _Pr _Pred, _Pj _Proj) { + // sort median element to middle + using _Diff = iter_difference_t<_It>; + const _Diff _Count = _Last - _First; + if (_Count > 40) { // Tukey's ninther + const _Diff _Step = (_Count + 1) >> 3; // +1 can't overflow because range was made inclusive in caller + const _Diff _Two_step = _Step << 1; // note: intentionally discards low-order bit + _Med3_unchecked(_First, _First + _Step, _First + _Two_step, _Pred, _Proj); + _Med3_unchecked(_Mid - _Step, _Mid, _Mid + _Step, _Pred, _Proj); + _Med3_unchecked(_Last - _Two_step, _Last - _Step, _Last, _Pred, _Proj); + _Med3_unchecked(_First + _Step, _Mid, _Last - _Step, _Pred, _Proj); + } else { + _Med3_unchecked(_First, _Mid, _Last, _Pred, _Proj); + } + } + + template + requires sortable<_It, _Pr, _Pj> + _NODISCARD constexpr subrange<_It> _Partition_by_median_guess_unchecked( + _It _First, _It _Last, _Pr _Pred, _Pj _Proj) { + // Choose a pivot, partition [_First, _Last) into elements less than pivot, elements equal to pivot, and + // elements greater than pivot; return the equal partition as a subrange. + + _It _Mid = _First + ((_Last - _First) >> 1); // shift for codegen + _RANGES _Guess_median_unchecked(_First, _Mid, _RANGES prev(_Last), _Pred, _Proj); + _It _Pfirst = _Mid; + _It _Plast = _RANGES next(_Pfirst); + + while (_First < _Pfirst + && !_STD invoke(_Pred, _STD invoke(_Proj, *_RANGES prev(_Pfirst)), _STD invoke(_Proj, *_Pfirst)) + && !_STD invoke(_Pred, _STD invoke(_Proj, *_Pfirst), _STD invoke(_Proj, *_RANGES prev(_Pfirst)))) { + --_Pfirst; + } + + while (_Plast < _Last + && !_STD invoke(_Pred, _STD invoke(_Proj, *_Plast), _STD invoke(_Proj, *_Pfirst)) + && !_STD invoke(_Pred, _STD invoke(_Proj, *_Pfirst), _STD invoke(_Proj, *_Plast))) { + ++_Plast; + } + + _It _Gfirst = _Plast; + _It _Glast = _Pfirst; + + for (;;) { // partition + for (; _Gfirst < _Last; ++_Gfirst) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Pfirst), _STD invoke(_Proj, *_Gfirst))) { + continue; + } else if (_STD invoke(_Pred, _STD invoke(_Proj, *_Gfirst), _STD invoke(_Proj, *_Pfirst))) { + break; + } else if (_Plast != _Gfirst) { + _RANGES iter_swap(_Plast, _Gfirst); + ++_Plast; + } else { + ++_Plast; + } + } + + for (; _First < _Glast; --_Glast) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_RANGES prev(_Glast)), _STD invoke(_Proj, *_Pfirst))) { + continue; + } else if (_STD invoke( + _Pred, _STD invoke(_Proj, *_Pfirst), _STD invoke(_Proj, *_RANGES prev(_Glast)))) { + break; + } else if (--_Pfirst != _RANGES prev(_Glast)) { + _RANGES iter_swap(_Pfirst, _RANGES prev(_Glast)); + } + } + + if (_Glast == _First && _Gfirst == _Last) { + return {_STD move(_Pfirst), _STD move(_Plast)}; + } + + if (_Glast == _First) { // no room at bottom, rotate pivot upward + if (_Plast != _Gfirst) { + _RANGES iter_swap(_Pfirst, _Plast); + } + + ++_Plast; + _RANGES iter_swap(_Pfirst, _Gfirst); + ++_Pfirst; + ++_Gfirst; + } else if (_Gfirst == _Last) { // no room at top, rotate pivot downward + if (--_Glast != --_Pfirst) { + _RANGES iter_swap(_Glast, _Pfirst); + } + + _RANGES iter_swap(_Pfirst, --_Plast); + } else { + _RANGES iter_swap(_Gfirst, --_Glast); + ++_Gfirst; + } + } + } + // clang-format on +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE stable_sort @@ -7765,7 +7917,7 @@ _CONSTEXPR20 void nth_element(_RanIt _First, _RanIt _Nth, _RanIt _Last, _Pr _Pre if (_UMid.second <= _UNth) { _UFirst = _UMid.second; } else if (_UMid.first <= _UNth) { - return; // Nth inside fat pivot, done + return; // _Nth is in the subrange of elements equal to the pivot; done } else { _ULast = _UMid.first; } @@ -7793,6 +7945,82 @@ void nth_element(_ExPo&&, _RanIt _First, _RanIt _Nth, _RanIt _Last) noexcept /* // not parallelized at present, parallelism expected to be feasible in a future release _STD nth_element(_First, _Nth, _Last); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::nth_element + class _Nth_element_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, class _Pr = ranges::less, class _Pj = identity> + requires sortable<_It, _Pr, _Pj> + constexpr _It operator()(_It _First, _It _Nth, _Se _Last, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Nth); + _Adl_verify_range(_Nth, _Last); + auto _UNth = _Get_unwrapped(_Nth); + auto _UFinal = _Get_final_iterator_unwrapped<_It>(_UNth, _STD move(_Last)); + _Seek_wrapped(_Nth, _UFinal); + + _Nth_element_common(_Get_unwrapped(_STD move(_First)), _STD move(_UNth), _STD move(_UFinal), + _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _Nth; + } + + template + requires sortable, _Pr, _Pj> + constexpr borrowed_iterator_t<_Rng> operator()( + _Rng&& _Range, iterator_t<_Rng> _Nth, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_RANGES begin(_Range), _Nth); + _Adl_verify_range(_Nth, _RANGES end(_Range)); + auto _UNth = _Get_unwrapped(_Nth); + auto _UFinal = [&] { + if constexpr (common_range<_Rng>) { + return _Uend(_Range); + } else if constexpr (sized_range<_Rng>) { + return _RANGES next(_Ubegin(_Range), _RANGES distance(_Range)); + } else { + return _RANGES next(_UNth, _Uend(_Range)); + } + }(); + _Seek_wrapped(_Nth, _UFinal); + + _Nth_element_common( + _Ubegin(_Range), _STD move(_UNth), _STD move(_UFinal), _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _Nth; + } + // clang-format on + private: + template + static constexpr void _Nth_element_common(_It _First, _It _Nth, _It _Last, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(random_access_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sortable<_It, _Pr, _Pj>); + + if (_Nth == _Last) { + return; // nothing to do + } + + while (_ISORT_MAX < _Last - _First) { // divide and conquer, ordering partition containing Nth + subrange<_It> _Mid = _RANGES _Partition_by_median_guess_unchecked(_First, _Last, _Pred, _Proj); + + if (_Mid.end() <= _Nth) { + _First = _Mid.end(); + } else if (_Mid.begin() <= _Nth) { + return; // _Nth is in the subrange of elements equal to the pivot; done + } else { + _Last = _Mid.begin(); + } + } + + // sort any remainder + _RANGES _Insertion_sort_common(_STD move(_First), _STD move(_Last), _STD move(_Pred), _STD move(_Proj)); + } + }; + + inline constexpr _Nth_element_fn nth_element{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE includes diff --git a/tests/std/test.lst b/tests/std/test.lst index d639d5e2c81..a8a70ccb762 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -271,6 +271,7 @@ tests\P0896R4_ranges_alg_mismatch tests\P0896R4_ranges_alg_move tests\P0896R4_ranges_alg_move_backward tests\P0896R4_ranges_alg_none_of +tests\P0896R4_ranges_alg_nth_element tests\P0896R4_ranges_alg_partition tests\P0896R4_ranges_alg_partition_copy tests\P0896R4_ranges_alg_partition_point diff --git a/tests/std/tests/P0896R4_ranges_alg_nth_element/env.lst b/tests/std/tests/P0896R4_ranges_alg_nth_element/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_nth_element/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_nth_element/test.cpp b/tests/std/tests/P0896R4_ranges_alg_nth_element/test.cpp new file mode 100644 index 00000000000..36b3f936650 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_nth_element/test.cpp @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include + +using namespace std; + +// Validate dangling story +STATIC_ASSERT(same_as{}, nullptr_to)), ranges::dangling>); +STATIC_ASSERT(same_as{}, nullptr_to)), int*>); + +using P = pair; + +struct instantiator { + static constexpr int keys[] = {7, 6, 5, 4, 3, 2, 1, 0}; + + template + static constexpr void call() { +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 +#pragma warning(suppress : 4127) // conditional expression is constant + if (!ranges::contiguous_range || !is_constant_evaluated()) +#endif // TRANSITION, VSO-938163 + { + using ranges::nth_element, ranges::all_of, ranges::find, ranges::iterator_t, ranges::less, ranges::none_of, + ranges::size; + + P input[size(keys)]; + const auto init = [&] { + for (size_t j = 0; j < size(keys); ++j) { + input[j] = P{keys[j], static_cast(10 + j)}; + } + }; + + // Validate range overload + for (int i = 0; i < int{size(keys)}; ++i) { + init(); + const R wrapped{input}; + const auto nth = wrapped.begin() + i; + const same_as> auto result = nth_element(wrapped, nth, less{}, get_first); + assert(result == wrapped.end()); + assert((*nth == P{i, static_cast(10 + (find(keys, i) - keys))})); + if (nth != wrapped.end()) { + assert(all_of(wrapped.begin(), nth, [&](auto&& x) { return get_first(x) <= get_first(*nth); })); + assert(all_of(nth, wrapped.end(), [&](auto&& x) { return get_first(*nth) <= get_first(x); })); + } + } + + // Validate iterator overload + for (int i = 0; i < int{size(keys)}; ++i) { + init(); + const R wrapped{input}; + const auto nth = wrapped.begin() + i; + const same_as> auto result = + nth_element(wrapped.begin(), nth, wrapped.end(), less{}, get_first); + assert(result == wrapped.end()); + assert((input[i] == P{i, static_cast(10 + (find(keys, i) - keys))})); + if (nth != wrapped.end()) { + assert(all_of(wrapped.begin(), nth, [&](auto&& x) { return get_first(x) <= get_first(*nth); })); + assert(all_of(nth, wrapped.end(), [&](auto&& x) { return get_first(*nth) <= get_first(x); })); + } + } + + { + // Validate empty range + const R range{}; + const same_as> auto result = nth_element(range, range.begin(), less{}, get_first); + assert(result == range.end()); + } + } + } +}; + +int main() { + STATIC_ASSERT((test_random(), true)); + test_random(); +} From e2912b6e488c351ef7130ae9f06d1a47745ac52d Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Sat, 1 Aug 2020 16:19:09 -0700 Subject: [PATCH 30/50] Implement ranges::ssize (#1076) --- stl/inc/concepts | 2 +- stl/inc/type_traits | 2 +- stl/inc/xutility | 57 ++++++++++++++----- stl/inc/yvals_core.h | 1 + .../P0896R4_ranges_range_machinery/test.cpp | 14 ++++- 5 files changed, 58 insertions(+), 18 deletions(-) diff --git a/stl/inc/concepts b/stl/inc/concepts index 2cc8f904bad..4da6a23f497 100644 --- a/stl/inc/concepts +++ b/stl/inc/concepts @@ -79,7 +79,7 @@ concept integral = is_integral_v<_Ty>; // CONCEPT signed_integral template -concept signed_integral = integral<_Ty> && _Ty(-1) < _Ty(0); +concept signed_integral = integral<_Ty> && static_cast<_Ty>(-1) < static_cast<_Ty>(0); // CONCEPT unsigned_integral template diff --git a/stl/inc/type_traits b/stl/inc/type_traits index 41438975b0f..477f3a47e12 100644 --- a/stl/inc/type_traits +++ b/stl/inc/type_traits @@ -895,7 +895,7 @@ template > struct _Sign_base { // determine whether integral type _Ty is signed or unsigned using _Uty = remove_cv_t<_Ty>; - static constexpr bool _Signed = _Uty(-1) < _Uty(0); + static constexpr bool _Signed = static_cast<_Uty>(-1) < static_cast<_Uty>(0); static constexpr bool _Unsigned = !_Signed; }; diff --git a/stl/inc/xutility b/stl/inc/xutility index cc9cf7e15e5..9c82bb27c55 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -751,20 +751,34 @@ concept indirectly_writable = requires(_It&& __i, _Ty&& __t) { const_cast&&>(*static_cast<_It&&>(__i)) = static_cast<_Ty&&>(__t); }; +// CONCEPT _Integer_like +// clang-format off +template +concept _Integer_like = _Is_nonbool_integral<_Ty>; // per the proposed resolution of LWG-3467 + +// CONCEPT _Signed_integer_like +template +concept _Signed_integer_like = _Integer_like<_Ty> && static_cast<_Ty>(-1) < static_cast<_Ty>(0); +// clang-format on + +// ALIAS TEMPLATE _Make_unsigned_like_t +template +using _Make_unsigned_like_t = make_unsigned_t<_Ty>; + +// ALIAS TEMPLATE _Make_signed_like_t +template +using _Make_signed_like_t = make_signed_t<_Ty>; // per the proposed resolution of LWG-3403 + // CONCEPT weakly_incrementable // clang-format off template concept weakly_incrementable = default_initializable<_Ty> && movable<_Ty> && requires(_Ty __i) { typename iter_difference_t<_Ty>; - requires signed_integral>; + requires _Signed_integer_like>; { ++__i } -> same_as<_Ty&>; __i++; }; -// ALIAS TEMPLATE _Make_unsigned_like_t -template -using _Make_unsigned_like_t = make_unsigned_t<_Ty>; - // CONCEPT incrementable template concept incrementable = regular<_Ty> && weakly_incrementable<_Ty> && requires(_Ty __t) { @@ -2849,12 +2863,12 @@ namespace ranges { // clang-format off template concept _Has_member = !disable_sized_range<_UnCV> && requires(_Ty __t) { - { _Fake_decay_copy(__t.size()) } -> integral; + { _Fake_decay_copy(__t.size()) } -> _Integer_like; }; template concept _Has_ADL = _Has_class_or_enum_type<_Ty> && !disable_sized_range<_UnCV> && requires(_Ty __t) { - { _Fake_decay_copy(size(__t)) } -> integral; + { _Fake_decay_copy(size(__t)) } -> _Integer_like; }; template @@ -3139,7 +3153,7 @@ namespace ranges { ~_Not_quite_object() = default; }; - // CLASS ranges::_Advance_fn + // VARIABLE ranges::advance class _Advance_fn : private _Not_quite_object { public: using _Not_quite_object::_Not_quite_object; @@ -3229,10 +3243,9 @@ namespace ranges { } }; - // VARIABLE ranges::advance inline constexpr _Advance_fn advance{_Not_quite_object::_Construct_tag{}}; - // CLASS ranges::_Distance_fn + // VARIABLE ranges::distance class _Distance_fn : private _Not_quite_object { public: using _Not_quite_object::_Not_quite_object; @@ -3271,10 +3284,26 @@ namespace ranges { } }; - // VARIABLE ranges::distance inline constexpr _Distance_fn distance{_Not_quite_object::_Construct_tag{}}; - // CLASS ranges::_Next_fn + // VARIABLE ranges::ssize + class _Ssize_fn { // Per the proposed resolution of LWG-3403 + public: + // clang-format off + template + _NODISCARD constexpr auto operator()(_Rng&& _Range) const requires requires { _RANGES size(_Range); } { + using _Sty = _Make_signed_like_t; + using _Ty = common_type_t, ptrdiff_t, _Sty>, _Sty>; + return static_cast<_Ty>(_RANGES size(_Range)); + } + // clang-format on + }; + + inline namespace _Cpos { + inline constexpr _Ssize_fn ssize; + } + + // VARIABLE ranges::next class _Next_fn : private _Not_quite_object { public: using _Not_quite_object::_Not_quite_object; @@ -3304,10 +3333,9 @@ namespace ranges { } }; - // VARIABLE ranges::next inline constexpr _Next_fn next{_Not_quite_object::_Construct_tag{}}; - // CLASS ranges::_Prev_fn + // VARIABLE ranges::prev class _Prev_fn : private _Not_quite_object { public: using _Not_quite_object::_Not_quite_object; @@ -3333,7 +3361,6 @@ namespace ranges { } }; - // VARIABLE ranges::prev inline constexpr _Prev_fn prev{_Not_quite_object::_Construct_tag{}}; // FUNCTION TEMPLATE _Find_last_iterator diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 41fa0568e92..6bf32c66d10 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -203,6 +203,7 @@ // P1871R1 disable_sized_sentinel_for // P1872R0 span Should Have size_type, Not index_type // P1878R1 Constraining Readable Types +// P1907R2 ranges::ssize // P1956R1 has_single_bit(), bit_ceil(), bit_floor(), bit_width() // P1959R0 Removing weak_equality And strong_equality // P1964R2 Replacing boolean With boolean-testable diff --git a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp index 10e217d99ad..f424befb4ee 100644 --- a/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_range_machinery/test.cpp @@ -61,6 +61,9 @@ concept CanEmpty = requires(R&& r) { ranges::empty(std::forward(r)); }; template concept CanSize = requires(R&& r) { ranges::size(std::forward(r)); }; +template +concept CanSSize = requires(R&& r) { ranges::ssize(std::forward(r)); }; + template concept CanSizeType = requires { typename ranges::range_size_t; }; @@ -116,6 +119,7 @@ STATIC_ASSERT(test_cpo(ranges::rend)); STATIC_ASSERT(test_cpo(ranges::crbegin)); STATIC_ASSERT(test_cpo(ranges::crend)); STATIC_ASSERT(test_cpo(ranges::size)); +STATIC_ASSERT(test_cpo(ranges::ssize)); STATIC_ASSERT(test_cpo(ranges::empty)); STATIC_ASSERT(test_cpo(ranges::data)); STATIC_ASSERT(test_cpo(ranges::cdata)); @@ -136,6 +140,7 @@ void test_cpo_ambiguity() { (void) crbegin(vri); (void) crend(vri); (void) size(vri); + (void) ssize(vri); (void) empty(vri); (void) data(vri); (void) cdata(vri); @@ -315,16 +320,20 @@ constexpr bool test_empty() { template constexpr bool test_size() { - // Validate ranges::size and ranges::sized_range + // Validate ranges::size, ranges::sized_range, and ranges::ssize STATIC_ASSERT(!is_valid || std::integral); STATIC_ASSERT(CanSize == is_valid); + STATIC_ASSERT(CanSSize == is_valid); STATIC_ASSERT(CanSizeType == is_valid); STATIC_ASSERT(ranges::sized_range == is_valid); if constexpr (is_valid) { STATIC_ASSERT(std::same_as())), Size>); STATIC_ASSERT(std::same_as, Size>); + using SignedSize = std::common_type_t>; + STATIC_ASSERT(std::same_as())), SignedSize>); + STATIC_ASSERT(CanEmpty); } @@ -1612,6 +1621,9 @@ namespace exhaustive_size_and_view_test { STATIC_ASSERT(ranges::sized_range == is_valid); if constexpr (is_valid) { STATIC_ASSERT(std::same_as())), Size>); + + using SignedSize = std::common_type_t>; + STATIC_ASSERT(std::same_as())), SignedSize>); } STATIC_ASSERT(ranges::view == IsView); From 57db210e30438e771bb0d6bf09bb34ae5a668e6e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 1 Aug 2020 16:36:24 -0700 Subject: [PATCH 31/50] Fix weak_ptr conversions, preserving control blocks for expired objects (#1104) Fixes #1102. --- stl/inc/memory | 55 ++++++++--- stl/inc/tuple | 4 +- .../test.cpp | 97 +++++++++++++++---- .../P0414R2_shared_ptr_for_arrays/test.cpp | 2 +- 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/stl/inc/memory b/stl/inc/memory index 5ee7a127acc..5b65f83450a 100644 --- a/stl/inc/memory +++ b/stl/inc/memory @@ -794,6 +794,40 @@ protected: } } + template + void _Weakly_convert_lvalue_avoiding_expired_conversions(const _Ptr_base<_Ty2>& _Other) noexcept { + // implement weak_ptr's copy converting ctor + if (_Other._Rep) { + _Rep = _Other._Rep; // always share ownership + _Rep->_Incwref(); + + if (_Rep->_Incref_nz()) { + _Ptr = _Other._Ptr; // keep resource alive during conversion, handling virtual inheritance + _Rep->_Decref(); + } else { + _STL_INTERNAL_CHECK(!_Ptr); + } + } else { + _STL_INTERNAL_CHECK(!_Ptr && !_Rep); + } + } + + template + void _Weakly_convert_rvalue_avoiding_expired_conversions(_Ptr_base<_Ty2>&& _Other) noexcept { + // implement weak_ptr's move converting ctor + _Rep = _Other._Rep; // always transfer ownership + _Other._Rep = nullptr; + + if (_Rep && _Rep->_Incref_nz()) { + _Ptr = _Other._Ptr; // keep resource alive during conversion, handling virtual inheritance + _Rep->_Decref(); + } else { + _STL_INTERNAL_CHECK(!_Ptr); + } + + _Other._Ptr = nullptr; + } + void _Incwref() const noexcept { if (_Rep) { _Rep->_Incwref(); @@ -2255,30 +2289,29 @@ _NODISCARD enable_if_t, shared_ptr<_Ty>> allocate_shared template class weak_ptr : public _Ptr_base<_Ty> { // class for pointer to reference counted resource public: - constexpr weak_ptr() noexcept {} // construct empty weak_ptr object + constexpr weak_ptr() noexcept {} - weak_ptr(const weak_ptr& _Other) noexcept { // construct weak_ptr object for resource pointed to by _Other - this->_Weakly_construct_from(_Other); + weak_ptr(const weak_ptr& _Other) noexcept { + this->_Weakly_construct_from(_Other); // same type, no conversion } template ::value, int> = 0> - weak_ptr(const shared_ptr<_Ty2>& _Other) noexcept { // construct weak_ptr object for resource owned by _Other - this->_Weakly_construct_from(_Other); + weak_ptr(const shared_ptr<_Ty2>& _Other) noexcept { + this->_Weakly_construct_from(_Other); // shared_ptr keeps resource alive during conversion } template ::value, int> = 0> - weak_ptr(const weak_ptr<_Ty2>& _Other) noexcept { // construct weak_ptr object for resource pointed to by _Other - this->_Weakly_construct_from(_Other.lock()); + weak_ptr(const weak_ptr<_Ty2>& _Other) noexcept { + this->_Weakly_convert_lvalue_avoiding_expired_conversions(_Other); } - weak_ptr(weak_ptr&& _Other) noexcept { // move construct from _Other + weak_ptr(weak_ptr&& _Other) noexcept { this->_Move_construct_from(_STD move(_Other)); } template ::value, int> = 0> - weak_ptr(weak_ptr<_Ty2>&& _Other) noexcept { // move construct from _Other - this->_Weakly_construct_from(_Other.lock()); - _Other.reset(); + weak_ptr(weak_ptr<_Ty2>&& _Other) noexcept { + this->_Weakly_convert_rvalue_avoiding_expired_conversions(_STD move(_Other)); } ~weak_ptr() noexcept { diff --git a/stl/inc/tuple b/stl/inc/tuple index 8cbc90d9e4d..ee6f40fb1e2 100644 --- a/stl/inc/tuple +++ b/stl/inc/tuple @@ -115,7 +115,7 @@ _INLINE_VAR constexpr bool _Tuple_nothrow_assignable_v = _Tuple_nothrow_assignable_v0 == sizeof...(_Srcs), _Dest, _Srcs...>; // STRUCT TEMPLATE _Tuple_convert_copy_val -// Constrain tuple's converting copy constructor (LWG-2549) +// Constrain tuple's copy converting constructor (LWG-2549) template struct _Tuple_convert_copy_val : true_type {}; @@ -125,7 +125,7 @@ struct _Tuple_convert_copy_val, _Uty> is_convertible&, _This>>> {}; // STRUCT TEMPLATE _Tuple_convert_move_val -// Constrain tuple's converting move constructor (LWG-2549) +// Constrain tuple's move converting constructor (LWG-2549) template struct _Tuple_convert_move_val : true_type {}; diff --git a/tests/std/tests/Dev10_851347_weak_ptr_virtual_inheritance/test.cpp b/tests/std/tests/Dev10_851347_weak_ptr_virtual_inheritance/test.cpp index 68863dd8e7d..c46ef9309f7 100644 --- a/tests/std/tests/Dev10_851347_weak_ptr_virtual_inheritance/test.cpp +++ b/tests/std/tests/Dev10_851347_weak_ptr_virtual_inheritance/test.cpp @@ -4,48 +4,109 @@ #include #include #include - using namespace std; +// Also test GH-1102 ": weak_ptr conversions don't preserve control blocks for expired objects" +template +[[nodiscard]] bool owner_equal(const weak_ptr& t, const weak_ptr& u) { + return !t.owner_before(u) && !u.owner_before(t); +} + +void test_owner_equal() { + shared_ptr sp_alive1(new int(0)); + shared_ptr sp_alive2(new int(0)); + shared_ptr sp_expiring3(new int(0)); + shared_ptr sp_expiring4(new int(0)); + + weak_ptr wp_empty; + weak_ptr wp_also_empty; + + weak_ptr wp_alive(sp_alive1); + weak_ptr wp_alive_same(sp_alive1); + weak_ptr wp_alive_different(sp_alive2); + + weak_ptr wp_expired(sp_expiring3); + weak_ptr wp_expired_same(sp_expiring3); + weak_ptr wp_expired_different(sp_expiring4); + + sp_expiring3.reset(); + sp_expiring4.reset(); + + assert(wp_empty.expired()); + assert(wp_also_empty.expired()); + + assert(!wp_alive.expired()); + assert(!wp_alive_same.expired()); + assert(!wp_alive_different.expired()); + + assert(wp_expired.expired()); + assert(wp_expired_same.expired()); + assert(wp_expired_different.expired()); + + assert(owner_equal(wp_empty, wp_also_empty)); + + assert(!owner_equal(wp_empty, wp_alive)); + assert(!owner_equal(wp_empty, wp_expired)); + + assert(!owner_equal(wp_alive, wp_empty)); + assert(!owner_equal(wp_expired, wp_empty)); + + assert(owner_equal(wp_alive, wp_alive_same)); + assert(owner_equal(wp_expired, wp_expired_same)); + + assert(!owner_equal(wp_alive, wp_alive_different)); + assert(!owner_equal(wp_alive, wp_expired)); + assert(!owner_equal(wp_expired, wp_alive)); + assert(!owner_equal(wp_expired, wp_expired_different)); +} + struct A { - int a; + int a{10}; }; -struct B : virtual public A { - int b; +struct B : virtual A { + int b{20}; }; -struct C : virtual public A { - int c; +struct C : virtual A { + int c{30}; }; -struct D : public B, public C { - int d; +struct D : B, C { + int d{40}; }; int main() { + test_owner_equal(); + shared_ptr spd(new D); - weak_ptr wpd(spd); - weak_ptr wpd2(spd); + const weak_ptr wpd_zero(spd); + weak_ptr wpd_one(spd); + weak_ptr wpd_two(spd); - spd.reset(); + weak_ptr wpa0(wpd_zero); + assert(!wpa0.expired()); + assert(owner_equal(wpa0, wpd_zero)); + assert(wpa0.lock()->a == 10); - weak_ptr wpa1(wpd); + spd.reset(); + weak_ptr wpa1(wpd_one); assert(wpa1.expired()); + assert(owner_equal(wpa1, wpd_zero)); weak_ptr wpa2; - - wpa2 = wpd; - + wpa2 = wpd_one; assert(wpa2.expired()); + assert(owner_equal(wpa2, wpd_zero)); - - weak_ptr wpa3(move(wpd)); + weak_ptr wpa3(move(wpd_one)); assert(wpa3.expired()); + assert(owner_equal(wpa3, wpd_zero)); weak_ptr wpa4; - wpa4 = move(wpd2); + wpa4 = move(wpd_two); assert(wpa4.expired()); + assert(owner_equal(wpa4, wpd_zero)); } diff --git a/tests/std/tests/P0414R2_shared_ptr_for_arrays/test.cpp b/tests/std/tests/P0414R2_shared_ptr_for_arrays/test.cpp index 26a2322fa9e..b7443129dc1 100644 --- a/tests/std/tests/P0414R2_shared_ptr_for_arrays/test.cpp +++ b/tests/std/tests/P0414R2_shared_ptr_for_arrays/test.cpp @@ -939,7 +939,7 @@ void test_LWG_2996() { assert(sp1.use_count() == 1); assert(sp1.get() == pz); - shared_ptr sp2(move(sp1)); // converting move ctor, old + shared_ptr sp2(move(sp1)); // move converting ctor, old assert(sp1.use_count() == 0); assert(sp1.get() == nullptr); assert(sp2.use_count() == 1); From 782cd650ec5ee95feb8128b1223f4f60987f84eb Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 2 Aug 2020 02:44:01 +0300 Subject: [PATCH 32/50] : Fix countl_zero to switch between lzcnt and bsr correctly (#1108) Fixes #1103. --- stl/inc/bit | 71 +++++++++++++++++++++++++++++++++++--------------- stl/inc/limits | 4 +-- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/stl/inc/bit b/stl/inc/bit index 0aa8f4451d1..9983b889003 100644 --- a/stl/inc/bit +++ b/stl/inc/bit @@ -127,21 +127,9 @@ extern int __isa_available; } template -_NODISCARD int _Checked_x86_x64_countl_zero(const _Ty _Val) noexcept { - constexpr int _Digits = numeric_limits<_Ty>::digits; - -#ifndef __AVX2__ - const bool _Have_lzcnt = __isa_available >= __ISA_AVAILABLE_AVX2; - // lzcnt (when it doesn't fall back to bsr) is defined correctly for zero - // bsr has undefined output for zero - if (!_Have_lzcnt && _Val == 0) { - return _Digits; - } -#endif // __AVX2__ +_NODISCARD int _Countl_zero_lzcnt(const _Ty _Val) noexcept { + constexpr int _Digits = numeric_limits<_Ty>::digits; - // We use lzcnt (actually bsr if lzcnt is not supported) now that we know - // we're not zero. We can do this because lzcnt and bsr share the same instruction - // encoding. if constexpr (_Digits <= 16) { return static_cast(__lzcnt16(_Val) - (16 - _Digits)); } else if constexpr (_Digits == 32) { @@ -151,24 +139,65 @@ _NODISCARD int _Checked_x86_x64_countl_zero(const _Ty _Val) noexcept { const unsigned int _High = _Val >> 32; const auto _Low = static_cast(_Val); if (_High == 0) { - return 32 + _Checked_x86_x64_countl_zero(_Low); + return 32 + _Countl_zero_lzcnt(_Low); } else { - return _Checked_x86_x64_countl_zero(_High); + return _Countl_zero_lzcnt(_High); } #else // ^^^ _M_IX86 / !_M_IX86 vvv return static_cast(__lzcnt64(_Val)); #endif // _M_IX86 } - // note: we don't need to call a fallback here because - // all supported x86 processors at least have bsr/bsf +} + +template +_NODISCARD int _Countl_zero_bsr(const _Ty _Val) noexcept { + constexpr int _Digits = numeric_limits<_Ty>::digits; + + unsigned long _Result; + if constexpr (_Digits <= 32) { + if (!_BitScanReverse(&_Result, _Val)) { + return _Digits; + } + } else { +#ifdef _M_IX86 + const unsigned int _High = _Val >> 32; + if (_BitScanReverse(&_Result, _High)) { + return static_cast(31 - _Result); + } + + const auto _Low = static_cast(_Val); + if (!_BitScanReverse(&_Result, _Low)) { + return _Digits; + } +#else // ^^^ _M_IX86 / !_M_IX86 vvv + if (!_BitScanReverse64(&_Result, _Val)) { + return _Digits; + } +#endif // _M_IX86 + } + return static_cast(_Digits - 1 - _Result); +} + +template +_NODISCARD int _Checked_x86_x64_countl_zero(const _Ty _Val) noexcept { +#ifdef __AVX2__ + return _Countl_zero_lzcnt(_Val); +#else // __AVX2__ + const bool _Definitely_have_lzcnt = __isa_available >= __ISA_AVAILABLE_AVX2; + if (_Definitely_have_lzcnt) { + return _Countl_zero_lzcnt(_Val); + } else { + return _Countl_zero_bsr(_Val); + } +#endif // __AVX2__ } template _NODISCARD int _Checked_x86_x64_popcount(const _Ty _Val) noexcept { - constexpr int _Digits = numeric_limits<_Ty>::digits; + constexpr int _Digits = numeric_limits<_Ty>::digits; #ifndef __AVX__ - const bool _Have_popcnt = __isa_available >= __ISA_AVAILABLE_SSE42; - if (!_Have_popcnt) { + const bool _Definitely_have_popcnt = __isa_available >= __ISA_AVAILABLE_SSE42; + if (!_Definitely_have_popcnt) { return _Popcount_fallback(_Val); } #endif // !defined(__AVX__) diff --git a/stl/inc/limits b/stl/inc/limits index 67a18dd7173..0b78a1b0396 100644 --- a/stl/inc/limits +++ b/stl/inc/limits @@ -1061,8 +1061,8 @@ _NODISCARD int _Checked_x86_x64_countr_zero(const _Ty _Val) noexcept { constexpr _Ty _Max = (numeric_limits<_Ty>::max)(); #ifndef __AVX2__ - const bool _Have_tzcnt = __isa_available >= __ISA_AVAILABLE_AVX2; - if (!_Have_tzcnt && _Val == 0) { + const bool _Definitely_have_tzcnt = __isa_available >= __ISA_AVAILABLE_AVX2; + if (!_Definitely_have_tzcnt && _Val == 0) { return _Digits; } #endif // __AVX2__ From a712fe5042b5ecf6c88766d3288ce348ad0235ae Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Sat, 1 Aug 2020 16:53:44 -0700 Subject: [PATCH 33/50] Enable tests Dev09_172666_tr1_tuple_odr and VSO_0000000_matching_npos_address (#1109) --- .../Dev09_172666_tr1_tuple_odr/__init__.py | 2 ++ .../custom_format.py | 23 +++++++++++++++++++ .../Dev09_172666_tr1_tuple_odr/lit.local.cfg | 10 ++++++++ .../{one.cpp => test.cpp} | 9 ++++---- .../{two.cpp => test2.cpp} | 0 .../__init__.py | 2 ++ .../custom_format.py | 23 +++++++++++++++++++ .../lit.local.cfg | 10 ++++++++ .../{main.cpp => test.cpp} | 0 .../{a.cpp => test2.cpp} | 0 10 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 tests/std/tests/Dev09_172666_tr1_tuple_odr/__init__.py create mode 100644 tests/std/tests/Dev09_172666_tr1_tuple_odr/custom_format.py create mode 100644 tests/std/tests/Dev09_172666_tr1_tuple_odr/lit.local.cfg rename tests/std/tests/Dev09_172666_tr1_tuple_odr/{one.cpp => test.cpp} (70%) rename tests/std/tests/Dev09_172666_tr1_tuple_odr/{two.cpp => test2.cpp} (100%) create mode 100644 tests/std/tests/VSO_0000000_matching_npos_address/__init__.py create mode 100644 tests/std/tests/VSO_0000000_matching_npos_address/custom_format.py create mode 100644 tests/std/tests/VSO_0000000_matching_npos_address/lit.local.cfg rename tests/std/tests/VSO_0000000_matching_npos_address/{main.cpp => test.cpp} (100%) rename tests/std/tests/VSO_0000000_matching_npos_address/{a.cpp => test2.cpp} (100%) diff --git a/tests/std/tests/Dev09_172666_tr1_tuple_odr/__init__.py b/tests/std/tests/Dev09_172666_tr1_tuple_odr/__init__.py new file mode 100644 index 00000000000..2ac2a854cb0 --- /dev/null +++ b/tests/std/tests/Dev09_172666_tr1_tuple_odr/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception diff --git a/tests/std/tests/Dev09_172666_tr1_tuple_odr/custom_format.py b/tests/std/tests/Dev09_172666_tr1_tuple_odr/custom_format.py new file mode 100644 index 00000000000..323790ec7cb --- /dev/null +++ b/tests/std/tests/Dev09_172666_tr1_tuple_odr/custom_format.py @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from pathlib import Path + +from stl.test.format import STLTestFormat, TestStep + + +class CustomTestFormat(STLTestFormat): + def getBuildSteps(self, test, lit_config, shared): + shared.exec_dir = test.getExecDir() + exe_source = Path(test.getSourcePath()) + test2_source = exe_source.parent / 'test2.cpp' + output_base = test.getOutputBaseName() + output_dir = test.getOutputDir() + + cmd, out_files, shared.exec_file = \ + test.cxx.executeBasedOnFlagsCmd([exe_source, test2_source], + output_dir, shared.exec_dir, + output_base, [], [], []) + + yield TestStep(cmd, shared.exec_dir, [exe_source, test2_source], + test.cxx.compile_env) diff --git a/tests/std/tests/Dev09_172666_tr1_tuple_odr/lit.local.cfg b/tests/std/tests/Dev09_172666_tr1_tuple_odr/lit.local.cfg new file mode 100644 index 00000000000..4c325b48119 --- /dev/null +++ b/tests/std/tests/Dev09_172666_tr1_tuple_odr/lit.local.cfg @@ -0,0 +1,10 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import Dev09_172666_tr1_tuple_odr.custom_format + +config.test_format = \ + Dev09_172666_tr1_tuple_odr.custom_format.CustomTestFormat(config.test_format.cxx, + config.test_format.execute_external, + config.test_format.build_executor, + config.test_format.test_executor) diff --git a/tests/std/tests/Dev09_172666_tr1_tuple_odr/one.cpp b/tests/std/tests/Dev09_172666_tr1_tuple_odr/test.cpp similarity index 70% rename from tests/std/tests/Dev09_172666_tr1_tuple_odr/one.cpp rename to tests/std/tests/Dev09_172666_tr1_tuple_odr/test.cpp index 05cd4dc827d..5f18f8dedea 100644 --- a/tests/std/tests/Dev09_172666_tr1_tuple_odr/one.cpp +++ b/tests/std/tests/Dev09_172666_tr1_tuple_odr/test.cpp @@ -15,11 +15,12 @@ inline bool test_wchar_t_minus() { // LNK2031: calling convention missing in metadata errors, which are irrelevant here. #if defined(_M_CEE_PURE) && !defined(_NATIVE_WCHAR_T_DEFINED) return true; -#else - return fs::file_size(fs::current_path() / "two.cpp") != 0u; -#endif +#else // ^^^ /clr:pure /Zc:wchar_t- / Other vvv + return fs::file_size(fs::current_path() / "Dev09_172666_tr1_tuple_odr.exe") != 0u; +#endif // defined(_M_CEE_PURE) && !defined(_NATIVE_WCHAR_T_DEFINED) } int main() { - assert(meow() == 1729 && test_wchar_t_minus()); + assert(meow() == 1729); + assert(test_wchar_t_minus()); } diff --git a/tests/std/tests/Dev09_172666_tr1_tuple_odr/two.cpp b/tests/std/tests/Dev09_172666_tr1_tuple_odr/test2.cpp similarity index 100% rename from tests/std/tests/Dev09_172666_tr1_tuple_odr/two.cpp rename to tests/std/tests/Dev09_172666_tr1_tuple_odr/test2.cpp diff --git a/tests/std/tests/VSO_0000000_matching_npos_address/__init__.py b/tests/std/tests/VSO_0000000_matching_npos_address/__init__.py new file mode 100644 index 00000000000..2ac2a854cb0 --- /dev/null +++ b/tests/std/tests/VSO_0000000_matching_npos_address/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception diff --git a/tests/std/tests/VSO_0000000_matching_npos_address/custom_format.py b/tests/std/tests/VSO_0000000_matching_npos_address/custom_format.py new file mode 100644 index 00000000000..323790ec7cb --- /dev/null +++ b/tests/std/tests/VSO_0000000_matching_npos_address/custom_format.py @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from pathlib import Path + +from stl.test.format import STLTestFormat, TestStep + + +class CustomTestFormat(STLTestFormat): + def getBuildSteps(self, test, lit_config, shared): + shared.exec_dir = test.getExecDir() + exe_source = Path(test.getSourcePath()) + test2_source = exe_source.parent / 'test2.cpp' + output_base = test.getOutputBaseName() + output_dir = test.getOutputDir() + + cmd, out_files, shared.exec_file = \ + test.cxx.executeBasedOnFlagsCmd([exe_source, test2_source], + output_dir, shared.exec_dir, + output_base, [], [], []) + + yield TestStep(cmd, shared.exec_dir, [exe_source, test2_source], + test.cxx.compile_env) diff --git a/tests/std/tests/VSO_0000000_matching_npos_address/lit.local.cfg b/tests/std/tests/VSO_0000000_matching_npos_address/lit.local.cfg new file mode 100644 index 00000000000..994f1aefbc5 --- /dev/null +++ b/tests/std/tests/VSO_0000000_matching_npos_address/lit.local.cfg @@ -0,0 +1,10 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import VSO_0000000_matching_npos_address.custom_format + +config.test_format = \ + VSO_0000000_matching_npos_address.custom_format.CustomTestFormat(config.test_format.cxx, + config.test_format.execute_external, + config.test_format.build_executor, + config.test_format.test_executor) diff --git a/tests/std/tests/VSO_0000000_matching_npos_address/main.cpp b/tests/std/tests/VSO_0000000_matching_npos_address/test.cpp similarity index 100% rename from tests/std/tests/VSO_0000000_matching_npos_address/main.cpp rename to tests/std/tests/VSO_0000000_matching_npos_address/test.cpp diff --git a/tests/std/tests/VSO_0000000_matching_npos_address/a.cpp b/tests/std/tests/VSO_0000000_matching_npos_address/test2.cpp similarity index 100% rename from tests/std/tests/VSO_0000000_matching_npos_address/a.cpp rename to tests/std/tests/VSO_0000000_matching_npos_address/test2.cpp From 8ec6b33a924f9eb01c5d3b37e1fe4e3f20ac9935 Mon Sep 17 00:00:00 2001 From: Billy O'Neal Date: Sat, 1 Aug 2020 17:02:51 -0700 Subject: [PATCH 34/50] P1001R2 execution::unseq (#1111) Resolves #44. --- stl/inc/execution | 83 +++++++++++-------- stl/inc/yvals_core.h | 18 +++- tests/std/include/instantiate_algorithms.hpp | 18 ++++ .../test.cpp | 48 +++++++---- .../VSO_0157762_feature_test_macros/test.cpp | 10 ++- 5 files changed, 119 insertions(+), 58 deletions(-) diff --git a/stl/inc/execution b/stl/inc/execution index 18ccbc1d496..728cc71f91a 100644 --- a/stl/inc/execution +++ b/stl/inc/execution @@ -75,42 +75,71 @@ constexpr size_t _Still_active = static_cast(-1); // EXECUTION POLICIES namespace execution { - class sequenced_policy { // request for sequential execution with termination + class sequenced_policy { + // indicates support for only sequential execution, and requests termination on exceptions public: using _Standard_execution_policy = int; static constexpr bool _Parallelize = false; + static constexpr bool _Ivdep = false; }; inline constexpr sequenced_policy seq{/* unspecified */}; - class parallel_policy { // request for parallel execution with termination + class parallel_policy { + // indicates support by element access functions for parallel execution with parallel forward progress + // guarantees, and requests termination on exceptions public: using _Standard_execution_policy = int; static constexpr bool _Parallelize = true; + static constexpr bool _Ivdep = true; }; inline constexpr parallel_policy par{/* unspecified */}; class parallel_unsequenced_policy { - // request for parallel execution without thread identity with termination + // indicates support by element access functions for parallel execution with weakly parallel forward progress + // guarantees, and requests termination on exceptions + // // (at this time, equivalent to parallel_policy) public: using _Standard_execution_policy = int; static constexpr bool _Parallelize = true; + static constexpr bool _Ivdep = true; }; inline constexpr parallel_unsequenced_policy par_unseq{/* unspecified */}; + +#if _HAS_CXX20 + class unsequenced_policy { + // indicates support by element access functions for weakly parallel forward progress guarantees, and for + // executing interleaved on the same thread, and requests termination on exceptions + // + // (at this time, equivalent to sequenced_policy except for the for_each family) + public: + using _Standard_execution_policy = int; + static constexpr bool _Parallelize = false; + static constexpr bool _Ivdep = true; + }; + + inline constexpr unsequenced_policy unseq{/* unspecified */}; +#endif // _HAS_CXX20 + } // namespace execution +// All of the above are execution policies: template <> -struct is_execution_policy : true_type {}; // sequenced_policy is an execution policy +struct is_execution_policy : true_type {}; template <> -struct is_execution_policy : true_type {}; // parallel_policy is an execution policy +struct is_execution_policy : true_type {}; template <> -struct is_execution_policy : true_type { -}; // parallel_unsequenced_policy is an execution policy +struct is_execution_policy : true_type {}; + +#if _HAS_CXX20 +template <> +struct is_execution_policy : true_type {}; +#endif // _HAS_CXX20 // STRUCT _Parallelism_resources_exhausted struct _Parallelism_resources_exhausted : exception { @@ -1216,6 +1245,8 @@ void for_each(_ExPo&&, _FwdIt _First, _FwdIt _Last, _Fn _Func) noexcept /* termi } } + _For_each_ivdep(_UFirst, _ULast, _Pass_fn(_Func)); + } else if constexpr (remove_reference_t<_ExPo>::_Ivdep) { _For_each_ivdep(_UFirst, _ULast, _Pass_fn(_Func)); } else { for (; _UFirst != _ULast; ++_UFirst) { @@ -1258,6 +1289,8 @@ _FwdIt for_each_n(_ExPo&&, _FwdIt _First, const _Diff _Count_raw, _Fn _Func) noe _CATCH_END } + _Seek_wrapped(_First, _For_each_n_ivdep(_UFirst, _Count, _Pass_fn(_Func))); + } else if constexpr (remove_reference_t<_ExPo>::_Ivdep) { _Seek_wrapped(_First, _For_each_n_ivdep(_UFirst, _Count, _Pass_fn(_Func))); } else { for (; 0 < _Count; --_Count, (void) ++_UFirst) { @@ -2281,17 +2314,6 @@ _NODISCARD _FwdIt search_n(_ExPo&&, const _FwdIt _First, _FwdIt _Last, const _Di } // PARALLEL FUNCTION TEMPLATE transform -template -_FwdIt2 _Transform_ivdep(_FwdIt1 _First, const _FwdIt1 _Last, _FwdIt2 _Dest, _Fn _Func) { - // unary op transform with independent loop bodies -#pragma loop(ivdep) - for (; _First != _Last; ++_First, (void) ++_Dest) { - *_Dest = _Func(*_First); - } - - return _Dest; -} - template struct _Static_partitioned_unary_transform2 { using _Diff = _Common_diff_t<_FwdIt1, _FwdIt2>; @@ -2311,7 +2333,7 @@ struct _Static_partitioned_unary_transform2 { const auto _Key = _Team._Get_next_key(); if (_Key) { const auto _Source = _Source_basis._Get_chunk(_Key); - _Transform_ivdep(_Source._First, _Source._Last, _Dest_basis._Get_chunk(_Key)._First, _Func); + _STD transform(_Source._First, _Source._Last, _Dest_basis._Get_chunk(_Key)._First, _Func); return _Cancellation_status::_Running; } @@ -2349,12 +2371,12 @@ _FwdIt2 transform(_ExPo&&, const _FwdIt1 _First, const _FwdIt1 _Last, _FwdIt2 _D _CATCH_END } - _Seek_wrapped(_Dest, _Transform_ivdep(_UFirst, _ULast, _UDest, _Pass_fn(_Func))); + _Seek_wrapped(_Dest, _STD transform(_UFirst, _ULast, _UDest, _Pass_fn(_Func))); return _Dest; } else { _Seek_wrapped( - _Dest, _Transform_ivdep(_UFirst, _ULast, - _Get_unwrapped_n(_Dest, _Idl_distance<_FwdIt1>(_UFirst, _ULast)), _Pass_fn(_Func))); + _Dest, _STD transform(_UFirst, _ULast, _Get_unwrapped_n(_Dest, _Idl_distance<_FwdIt1>(_UFirst, _ULast)), + _Pass_fn(_Func))); return _Dest; } } else { @@ -2364,17 +2386,6 @@ _FwdIt2 transform(_ExPo&&, const _FwdIt1 _First, const _FwdIt1 _Last, _FwdIt2 _D } } -template -_FwdIt3 _Transform_ivdep(_FwdIt1 _First1, const _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt3 _Dest, _Fn _Func) { - // binary op transform with independent loop bodies -#pragma loop(ivdep) - for (; _First1 != _Last1; ++_First1, (void) ++_First2, ++_Dest) { - *_Dest = _Func(*_First1, *_First2); - } - - return _Dest; -} - template struct _Static_partitioned_binary_transform2 { using _Diff = _Common_diff_t<_FwdIt1, _FwdIt2, _FwdIt3>; @@ -2396,7 +2407,7 @@ struct _Static_partitioned_binary_transform2 { const auto _Key = _Team._Get_next_key(); if (_Key) { const auto _Source1 = _Source1_basis._Get_chunk(_Key); - _Transform_ivdep(_Source1._First, _Source1._Last, _Source2_basis._Get_chunk(_Key)._First, + _STD transform(_Source1._First, _Source1._Last, _Source2_basis._Get_chunk(_Key)._First, _Dest_basis._Get_chunk(_Key)._First, _Func); return _Cancellation_status::_Running; } @@ -2442,11 +2453,11 @@ _FwdIt3 transform(_ExPo&&, const _FwdIt1 _First1, const _FwdIt1 _Last1, const _F _CATCH_END } - _Seek_wrapped(_Dest, _Transform_ivdep(_UFirst1, _ULast1, _UFirst2, _UDest, _Pass_fn(_Func))); + _Seek_wrapped(_Dest, _STD transform(_UFirst1, _ULast1, _UFirst2, _UDest, _Pass_fn(_Func))); return _Dest; } else { const auto _Count = _Idl_distance<_FwdIt1>(_UFirst1, _ULast1); - _Seek_wrapped(_Dest, _Transform_ivdep(_UFirst1, _ULast1, _Get_unwrapped_n(_First2, _Count), + _Seek_wrapped(_Dest, _STD transform(_UFirst1, _ULast1, _Get_unwrapped_n(_First2, _Count), _Get_unwrapped_n(_Dest, _Count), _Pass_fn(_Func))); return _Dest; } diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 6bf32c66d10..bc55f3dcc29 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -173,6 +173,7 @@ // (partially implemented, missing noop coroutines) // P0919R3 Heterogeneous Lookup For Unordered Containers // P0966R1 string::reserve() Should Not Shrink +// P1001R2 execution::unseq // P1006R1 constexpr For pointer_traits::pointer_to() // P1023R0 constexpr For std::array Comparisons // P1024R3 Enhancing span Usability @@ -225,6 +226,10 @@ // C++ allows an implementation to implement parallel algorithms as calls to the serial algorithms. // This implementation parallelizes several common algorithm calls, but not all. // +// std::execution::unseq has no direct analogue for any optimizer we target as of 2020-07-29, +// though we will map it to #pragma loop(ivdep) for the for_each algorithms only as these are the only algorithms where +// the library does not need to introduce inter-loop-body dependencies to accomplish the algorithm's goals. +// // The following algorithms are parallelized. // * adjacent_difference // * adjacent_find @@ -1091,10 +1096,7 @@ #if _HAS_STD_BYTE #define __cpp_lib_byte 201603L #endif // _HAS_STD_BYTE -#define __cpp_lib_clamp 201603L -#ifndef _M_CEE -#define __cpp_lib_execution 201603L -#endif // _M_CEE +#define __cpp_lib_clamp 201603L #define __cpp_lib_filesystem 201703L #define __cpp_lib_gcd_lcm 201606L #define __cpp_lib_hardware_interference_size 201703L @@ -1185,6 +1187,14 @@ #define __cpp_lib_unwrap_ref 201811L #endif // _HAS_CXX20 +#ifndef _M_CEE +#if _HAS_CXX20 +#define __cpp_lib_execution 201902L // P1001R2 execution::unseq +#elif _HAS_CXX17 +#define __cpp_lib_execution 201603L // P0024R2 Parallel Algorithms +#endif // language mode +#endif // _M_CEE + #if _HAS_CXX20 #define __cpp_lib_array_constexpr 201811L // P1032R1 Miscellaneous constexpr #elif _HAS_CXX17 // ^^^ _HAS_CXX20 / _HAS_CXX17 vvv diff --git a/tests/std/include/instantiate_algorithms.hpp b/tests/std/include/instantiate_algorithms.hpp index 47d81e69976..5d19929cb36 100644 --- a/tests/std/include/instantiate_algorithms.hpp +++ b/tests/std/include/instantiate_algorithms.hpp @@ -415,6 +415,9 @@ namespace std_testing { test_exec_fwd1_fwd2(std::execution::seq, fwd1, fwd2); test_exec_fwd1_fwd2(std::execution::par, fwd1, fwd2); test_exec_fwd1_fwd2(std::execution::par_unseq, fwd1, fwd2); +#if _HAS_CXX20 + test_exec_fwd1_fwd2(std::execution::unseq, fwd1, fwd2); +#endif // _HAS_CXX20 #endif // HAS_PARALLEL_ALGORITHMS (void) std::find_end(fwd1, fwd1, fwd2, fwd2); @@ -503,6 +506,9 @@ namespace std_testing { test_exec_fwd1(std::execution::seq, fwd1); test_exec_fwd1(std::execution::par, fwd1); test_exec_fwd1(std::execution::par_unseq, fwd1); +#if _HAS_CXX20 + test_exec_fwd1(std::execution::unseq, fwd1); +#endif // _HAS_CXX20 #endif // HAS_PARALLEL_ALGORITHMS test_fwd1_fwd2(fwd1, FWDIT); @@ -592,6 +598,9 @@ namespace std_testing { test_exec_bid1_bid2_xxx_backward(std::execution::seq, bid1, bid2); test_exec_bid1_bid2_xxx_backward(std::execution::par, bid1, bid2); test_exec_bid1_bid2_xxx_backward(std::execution::par_unseq, bid1, bid2); +#if _HAS_CXX20 + test_exec_bid1_bid2_xxx_backward(std::execution::unseq, bid1, bid2); +#endif // _HAS_CXX20 #endif // HAS_PARALLEL_ALGORITHMS std::copy_backward(bid1, bid1, bid2); @@ -615,6 +624,9 @@ namespace std_testing { test_exec_bid1_fwd1(std::execution::seq, bid1, fwd1); test_exec_bid1_fwd1(std::execution::par, bid1, fwd1); test_exec_bid1_fwd1(std::execution::par_unseq, bid1, fwd1); +#if _HAS_CXX20 + test_exec_bid1_fwd1(std::execution::unseq, bid1, fwd1); +#endif // _HAS_CXX20 } template @@ -653,6 +665,9 @@ namespace std_testing { test_exec_bid1(std::execution::seq, bid1); test_exec_bid1(std::execution::par, bid1); test_exec_bid1(std::execution::par_unseq, bid1); +#if _HAS_CXX20 + test_exec_bid1(std::execution::unseq, bid1); +#endif // _HAS_CXX20 #endif // HAS_PARALLEL_ALGORITHMS std::reverse(bid1, bid1); @@ -700,6 +715,9 @@ namespace std_testing { test_exec_ran(std::execution::seq, ran); test_exec_ran(std::execution::par, ran); test_exec_ran(std::execution::par_unseq, ran); +#if _HAS_CXX20 + test_exec_ran(std::execution::unseq, ran); +#endif // _HAS_CXX20 #endif // HAS_PARALLEL_ALGORITHMS #if _HAS_AUTO_PTR_ETC diff --git a/tests/std/tests/P0024R2_parallel_algorithms_for_each/test.cpp b/tests/std/tests/P0024R2_parallel_algorithms_for_each/test.cpp index bf168a6522d..f3dc7a969c3 100644 --- a/tests/std/tests/P0024R2_parallel_algorithms_for_each/test.cpp +++ b/tests/std/tests/P0024R2_parallel_algorithms_for_each/test.cpp @@ -48,26 +48,40 @@ const auto call_only_once = [](atomic& b) { assert(!b.exchange(true)); }; const auto atomic_identity = [](atomic& b) { return b.load(); }; template