From 6d441a10f420363d0b0299d650df4812dbf097bd Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Sat, 11 Jul 2020 15:30:17 -0700 Subject: [PATCH] Implement most ranges partition algorithms (#976) Includes `ranges::is_partitioned`, `ranges::partition`, `ranges::partition_copy`, and `ranges::partition_point`. --- stl/inc/algorithm | 330 +++++++++++++++++- tests/std/include/range_algorithm_support.hpp | 3 + tests/std/test.lst | 3 + .../P0896R4_ranges_alg_partition/env.lst | 4 + .../P0896R4_ranges_alg_partition/test.cpp | 115 ++++++ .../P0896R4_ranges_alg_partition_copy/env.lst | 4 + .../test.cpp | 134 +++++++ .../env.lst | 4 + .../test.cpp | 64 ++++ 9 files changed, 649 insertions(+), 12 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_partition/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_partition/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_partition_copy/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp create mode 100644 tests/std/tests/P0896R4_ranges_alg_partition_point/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 16e04bb6d9..c8719fbc80 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -84,18 +84,18 @@ namespace ranges { #endif // __clang__ // STRUCT TEMPLATE in_fun_result - template + template struct in_fun_result { [[no_unique_address]] _In in; - [[no_unique_address]] _Fn fun; + [[no_unique_address]] _Fun fun; - template <_Convertible_from _IIn, _Convertible_from _FFn> - constexpr operator in_fun_result<_IIn, _FFn>() const& { + template <_Convertible_from _IIn, _Convertible_from _FFun> + constexpr operator in_fun_result<_IIn, _FFun>() const& { return {in, fun}; } - template <_Convertible_from<_In> _IIn, _Convertible_from<_Fn> _FFn> - constexpr operator in_fun_result<_IIn, _FFn>() && { + template <_Convertible_from<_In> _IIn, _Convertible_from<_Fun> _FFun> + constexpr operator in_fun_result<_IIn, _FFun>() && { return {_STD move(in), _STD move(fun)}; } }; @@ -324,8 +324,8 @@ _FwdIt for_each_n(_ExPo&& _Exec, _FwdIt _First, _Diff _Count_raw, _Fn _Func) noe #ifdef __cpp_lib_concepts namespace ranges { // ALIAS TEMPLATE for_each_result - template - using for_each_result = in_fun_result<_It, _Fn>; + template + using for_each_result = in_fun_result<_In, _Fun>; // VARIABLE ranges::for_each class _For_each_fn : private _Not_quite_object { @@ -357,8 +357,8 @@ namespace ranges { inline constexpr _For_each_fn for_each{_Not_quite_object::_Construct_tag{}}; // ALIAS TEMPLATE for_each_n_result - template - using for_each_n_result = in_fun_result<_It, _Fn>; + template + using for_each_n_result = in_fun_result<_In, _Fun>; // VARIABLE ranges::for_each_n class _For_each_n_fn : private _Not_quite_object { @@ -1768,6 +1768,77 @@ pair<_FwdIt2, _FwdIt3> partition_copy(_ExPo&&, _FwdIt1 _First, _FwdIt1 _Last, _F _REQUIRE_PARALLEL_ITERATOR(_FwdIt3); return _STD partition_copy(_First, _Last, _Dest_true, _Dest_false, _Pass_fn(_Pred)); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE partition_copy_result + template + using partition_copy_result = in_out_out_result<_In, _Out1, _Out2>; + + // VARIABLE ranges::partition_copy + class _Partition_copy_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out1, weakly_incrementable _Out2, + class _Pj = identity, indirect_unary_predicate> _Pr> + requires indirectly_copyable<_It, _Out1> && indirectly_copyable<_It, _Out2> + constexpr partition_copy_result<_It, _Out1, _Out2> operator()( + _It _First, _Se _Last, _Out1 _Dest_true, _Out2 _Dest_false, _Pr _Pred, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Partition_copy_unchecked(_Get_unwrapped(_STD move(_First)), + _Get_unwrapped(_STD move(_Last)), _Get_unwrapped_unverified(_STD move(_Dest_true)), + _Get_unwrapped_unverified(_STD move(_Dest_false)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + _Seek_wrapped(_First, _STD move(_UResult.in)); + _Seek_wrapped(_Dest_true, _STD move(_UResult.out1)); + _Seek_wrapped(_Dest_false, _STD move(_UResult.out2)); + return {_STD move(_First), _STD move(_Dest_true), _STD move(_Dest_false)}; + } + + template , _Pj>> _Pr> + requires indirectly_copyable, _Out1> && indirectly_copyable, _Out2> + constexpr partition_copy_result, _Out1, _Out2> operator()( + _Rng&& _Range, _Out1 _Dest_true, _Out2 _Dest_false, _Pr _Pred, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + auto _UResult = _Partition_copy_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), + _Get_unwrapped_unverified(_STD move(_Dest_true)), _Get_unwrapped_unverified(_STD move(_Dest_false)), + _Pass_fn(_Pred), _Pass_fn(_Proj)); + _Seek_wrapped(_First, _STD move(_UResult.in)); + _Seek_wrapped(_Dest_true, _STD move(_UResult.out1)); + _Seek_wrapped(_Dest_false, _STD move(_UResult.out2)); + return {_STD move(_First), _STD move(_Dest_true), _STD move(_Dest_false)}; + } + // clang-format on + private: + template + _NODISCARD static constexpr partition_copy_result<_It, _Out1, _Out2> _Partition_copy_unchecked( + _It _First, const _Se _Last, _Out1 _Dest_true, _Out2 _Dest_false, _Pr _Pred, _Pj _Proj) { + // copy true partition to _Dest_true, false to _Dest_false + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out1> && weakly_incrementable<_Out2>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_copyable<_It, _Out1> && indirectly_copyable<_It, _Out2>); + + for (; _First != _Last; ++_First) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + *_Dest_true = *_First; + ++_Dest_true; + } else { + *_Dest_false = *_First; + ++_Dest_false; + } + } + + return {_STD move(_First), _STD move(_Dest_true), _STD move(_Dest_false)}; + } + }; + + inline constexpr _Partition_copy_fn partition_copy{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE is_partitioned @@ -1802,6 +1873,59 @@ template _Se, class _Pj = identity, + indirect_unary_predicate> _Pr> + _NODISCARD constexpr bool operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + return _Is_partitioned_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 _Is_partitioned_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + } + + private: + template + _NODISCARD static constexpr bool _Is_partitioned_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + // test if [_First, _Last) is partitioned with respect to _Pred and _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) { // skip true partition + if (_First == _Last) { + return true; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + break; + } + } + + while (++_First != _Last) { // verify false partition + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + return false; // found out of place element + } + } + + return true; + } + }; + + inline constexpr _Is_partitioned_fn is_partitioned{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE partition_point template _NODISCARD _CONSTEXPR20 _FwdIt partition_point(_FwdIt _First, _FwdIt _Last, _Pr _Pred) { @@ -1827,6 +1951,105 @@ _NODISCARD _CONSTEXPR20 _FwdIt partition_point(_FwdIt _First, _FwdIt _Last, _Pr return _First; } +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::partition_point + class _Partition_point_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Pj = identity, + 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)); + if constexpr (sized_sentinel_for<_Se, _It>) { + const auto _Length = _Get_unwrapped(_STD move(_Last)) - _UFirst; + _UFirst = _Partition_point_n_unchecked(_STD move(_UFirst), _Length, _Pass_fn(_Pred), _Pass_fn(_Proj)); + } else { + _UFirst = _Partition_point_unchecked( + _STD move(_UFirst), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + } + + _Seek_wrapped(_First, _STD move(_UFirst)); + return _First; + } + + template , _Pj>> _Pr> + _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { + if constexpr (sized_range<_Rng>) { + const auto _Length = _RANGES distance(_Range); + auto _UFirst = _Partition_point_n_unchecked(_Ubegin(_Range), _Length, _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _Rewrap_iterator(_Range, _STD move(_UFirst)); + } else { + auto _UFirst = + _Partition_point_unchecked(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _Rewrap_iterator(_Range, _STD move(_UFirst)); + } + } + + private: + template + _NODISCARD static constexpr _It _Partition_point_n_unchecked( + _It _First, iter_difference_t<_It> _Length, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + _STL_INTERNAL_CHECK(_Length >= 0); + + while (_Length > 0) { + const auto _Half = static_cast>(_Length / 2); + auto _Mid = _RANGES next(_First, _Half); + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Mid))) { // _Mid is before the partition point + _First = _STD move(_Mid); + ++_First; + _Length -= _Half; + --_Length; + } else { // _Mid is at or past the partition point + _Length = _Half; + } + } + + return _First; + } + + template + _NODISCARD static constexpr _It _Partition_point_unchecked(_It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>); + + // Instead of blindly seeking the end of the range, probe elements at exponentially increasing intervals to + // find an element past the partition point. + iter_difference_t<_It> _Skip = 2; + for (;;) { + auto _Mid = _First; + _Skip -= _RANGES advance(_Mid, _Skip, _Last); + if (_Mid == _Last) { // we've located the end of the range + break; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_Mid))) { // _Mid is at or past the partition point + break; + } + + _First = _STD move(_Mid); + ++_First; + + using _Uty = _Make_unsigned_like_t>; + if (static_cast<_Uty>(_Skip) <= (static_cast<_Uty>(-1) >> 1)) { + _Skip <<= 1; + } + } + + return _Partition_point_n_unchecked(_STD move(_First), _Skip, _Pred, _Proj); + } + }; + + inline constexpr _Partition_point_fn partition_point{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE _Equal_rev_pred_unchecked #if _HAS_IF_CONSTEXPR template @@ -4136,6 +4359,89 @@ _FwdIt partition(_FwdIt _First, const _FwdIt _Last, _Pr _Pred) { #if _HAS_CXX17 template = 0> _FwdIt partition(_ExPo&& _Exec, _FwdIt _First, const _FwdIt _Last, _Pr _Pred) noexcept; // terminates + +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::partition + class _Partition_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Pj = identity, + indirect_unary_predicate> _Pr> + constexpr subrange<_It> operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Partition_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> + 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)); + return _Rewrap_subrange>(_Range, _STD move(_UResult)); + } + // clang-format on + private: + template + _NODISCARD static constexpr subrange<_It> _Partition_unchecked(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj) { + _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>>); + + if constexpr (_Bidi_common<_It, _Se>) { + auto _Saved_last = _Last; + for (;; ++_First) { // find any out-of-order pair + for (;; ++_First) { // skip in-place elements at beginning + if (_First == _Last) { + return {_STD move(_First), _STD move(_Saved_last)}; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + break; + } + } + + do { // skip in-place elements at end + --_Last; + if (_First == _Last) { + return {_STD move(_First), _STD move(_Saved_last)}; + } + } while (!_STD invoke(_Pred, _STD invoke(_Proj, *_Last))); + + _RANGES iter_swap(_First, _Last); // out of place, swap and loop + } + + return {_STD move(_First), _STD move(_Saved_last)}; + } else { + for (;; ++_First) { // skip in-place elements at beginning + if (_First == _Last) { + return {_First, _First}; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) { + break; + } + } + + auto _Next = _First; + while (++_Next != _Last) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Next))) { + _RANGES iter_swap(_First, _Next); // out of place, swap and loop + ++_First; + } + } + + return {_STD move(_First), _STD move(_Next)}; + } + } + }; + + inline constexpr _Partition_fn partition{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE stable_partition @@ -6620,8 +6926,8 @@ _NODISCARD pair<_FwdIt, _FwdIt> minmax_element(_ExPo&&, _FwdIt _First, _FwdIt _L #ifdef __cpp_lib_concepts namespace ranges { // ALIAS TEMPLATE minmax_element_result - template - using minmax_element_result = min_max_result<_It>; + template + using minmax_element_result = min_max_result<_Ty>; // VARIABLE ranges::minmax_element template diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index 886ae84181..523fa2f2cc 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -40,6 +40,9 @@ namespace detail { } // namespace detail constexpr bool is_permissive = detail::Derived::test(); +template +inline constexpr T* nullptr_to = nullptr; + template struct borrowed { // borrowed is a borrowed_range; borrowed is not int* begin() const; diff --git a/tests/std/test.lst b/tests/std/test.lst index 01e956f4fc..14730786fb 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -263,6 +263,9 @@ tests\P0896R4_ranges_alg_minmax tests\P0896R4_ranges_alg_mismatch tests\P0896R4_ranges_alg_move 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_replace_if tests\P0896R4_ranges_alg_search tests\P0896R4_ranges_alg_search_n diff --git a/tests/std/tests/P0896R4_ranges_alg_partition/env.lst b/tests/std/tests/P0896R4_ranges_alg_partition/env.lst new file mode 100644 index 0000000000..f3ccc8613c --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_partition/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_partition/test.cpp b/tests/std/tests/P0896R4_ranges_alg_partition/test.cpp new file mode 100644 index 0000000000..785d6ef20d --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_partition/test.cpp @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Covers ranges::is_partitioned and ranges::partition (and bits of ranges::partition_point) + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +#define ASSERT(...) assert((__VA_ARGS__)) + +using P = pair; + +constexpr auto is_even = [](int i) { return i % 2 == 0; }; + +// Validate dangling story +STATIC_ASSERT(same_as{}, is_even)), ranges::dangling>); +STATIC_ASSERT(same_as{}, is_even)), ranges::subrange>); + +struct empty_test { + template + static constexpr void call() { + // Validate empty ranges + using ranges::is_partitioned, ranges::partition, ranges::partition_point; + { + Range range{}; + ASSERT(is_partitioned(range, is_even, get_first)); + } + { + Range range{}; + ASSERT(is_partitioned(range.begin(), range.end(), is_even, get_first)); + } + + if constexpr (ranges::forward_range) { + const Range range{}; + { + const auto result = partition(range, is_even, get_first); + ASSERT(result.begin() == range.end()); + ASSERT(result.end() == range.end()); + } + { + const auto result = partition(range.begin(), range.end(), is_even, get_first); + ASSERT(result.begin() == range.end()); + ASSERT(result.end() == range.end()); + } + } + } +}; + +struct partition_test { + static constexpr array elements = {P{0, 42}, P{1, 42}, P{2, 42}, P{3, 42}, P{4, 42}, P{5, 42}, P{6, 42}, P{7, 42}}; + + template + static constexpr void call() { + using ranges::is_partitioned, ranges::partition, ranges::partition_point, ranges::is_permutation, + ranges::subrange; + + auto pairs = elements; + + { + Range range{pairs}; + ASSERT(!is_partitioned(range, is_even, get_first)); + } + { + Range range{pairs}; + ASSERT(!is_partitioned(range.begin(), range.end(), is_even, get_first)); + } + +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 + if (!is_constant_evaluated()) +#endif // TRANSITION, VSO-938163 + { + if constexpr (ranges::forward_range) { + const Range range{pairs}; + const auto mid = ranges::next(range.begin(), 4); + + { + auto result = partition(range, is_even, get_first); + ASSERT(result.begin() == mid); + ASSERT(result.end() == range.end()); + } + ASSERT(is_permutation(subrange{range.begin(), mid}, array{0, 2, 4, 6}, ranges::equal_to{}, get_first)); + ASSERT(is_partitioned(range, is_even, get_first)); + ASSERT(partition_point(range, is_even, get_first) == mid); + + pairs = elements; + + { + auto result = partition(range.begin(), range.end(), is_even, get_first); + ASSERT(result.begin() == mid); + ASSERT(result.end() == range.end()); + } + ASSERT(is_permutation(subrange{range.begin(), mid}, array{0, 2, 4, 6}, ranges::equal_to{}, get_first)); + ASSERT(is_partitioned(range.begin(), range.end(), is_even, get_first)); + ASSERT(partition_point(range.begin(), range.end(), is_even, get_first) == mid); + } + } + } +}; + +int main() { + STATIC_ASSERT((test_in(), true)); + test_in(); + + STATIC_ASSERT((test_in(), true)); + test_in(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_partition_copy/env.lst b/tests/std/tests/P0896R4_ranges_alg_partition_copy/env.lst new file mode 100644 index 0000000000..f3ccc8613c --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_partition_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_partition_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp new file mode 100644 index 0000000000..12d3d4cbd8 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +#define ASSERT(...) assert((__VA_ARGS__)) + +using P = pair; + +constexpr auto is_even = [](int i) { return i % 2 == 0; }; + +// Validate that partition_copy_result aliases in_out_out_result +STATIC_ASSERT( + same_as, ranges::in_out_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, nullptr_to, nullptr_to, is_even)), + ranges::partition_copy_result>); +STATIC_ASSERT(same_as{}, nullptr_to, nullptr_to, is_even)), + ranges::partition_copy_result>); + +struct empty_test { + template > Out1, + indirectly_writable> Out2> + static constexpr void call() { + // Validate empty ranges + using ranges::partition_copy, ranges::partition_copy_result, ranges::iterator_t; + + { + Range range{}; + auto result = partition_copy(range, Out1{}, Out2{}, is_even, get_first); + STATIC_ASSERT(same_as, Out1, Out2>>); + ASSERT(result.in == range.end()); + ASSERT(result.out1.peek() == nullptr); + ASSERT(result.out2.peek() == nullptr); + } + { + Range range{}; + auto result = partition_copy(range.begin(), range.end(), Out1{}, Out2{}, is_even, get_first); + STATIC_ASSERT( + same_as, Out1, Out2>>); + ASSERT(result.in == range.end()); + ASSERT(result.out1.peek() == nullptr); + ASSERT(result.out2.peek() == nullptr); + } + } +}; + +struct partition_copy_test { + static constexpr int N = 32; + + template > O1, + indirectly_writable> O2> + static constexpr void call() { + using ranges::partition_copy; + + P source[N]; + for (int i = 0; i < N; ++i) { + source[i] = {i, 42}; + } + + for (int i = 0; i < N; ++i) { + P dest[N]; + ranges::fill(dest, P{-1, 13}); + + const R range{source}; + auto result = partition_copy( + range, O1{dest}, O2{dest + i}, [i](int x) { return x < i; }, get_first); + assert(result.in == range.end()); + assert(result.out1.peek() == dest + i); + assert(result.out2.peek() == dest + N); + assert(ranges::equal(source, dest)); + } + } +}; + +template +constexpr void run_tests() { + // Call Instantiator::template call() with a range whose element type is Elem, and two iterators to + // which Elem is writable, whose properties are "interesting" for ranges::partition_copy. What combinations of + // properties are "interesting"? + + // For the input range, the algorithm simply unwraps iterators and chugs through looking for the end. It doesn't + // * take advantage of any capabilities provided by stronger-than-input categories, + // * care if the sentinel and iterator have the same type, + // * care if it can difference iterators with sentinels or each other, or + // * care about the size of the input range at all. (It can't even use size info to check the outputs, because we + // don't how many of the input elements will be written through each output.) + // TLDR: One input range with a proxy reference type and no other notable properties (the so-called "weakest" input + // range) suffices. + + // For the outputs, both of which are treated equivalently, the algorithm is similarly oblivious to properties other + // than reference type and the ability to unwrap/rewrap. These could simply be the "weakest" writable iterator type + // in with_writable_iterators. + + // Out of simple paranoia, let's permute ProxyRef; seven extra pointless tests won't hurt. + + using test::range, test::iterator, test::input, test::output, test::CanCompare, test::CanDifference, test::Common, + test::ProxyRef, test::Sized; + + using proxy_range = range; + using non_proxy_range = range; + using proxy_iterator = iterator, CanDifference::no, CanCompare::no, ProxyRef::yes>; + using non_proxy_iterator = iterator, CanDifference::no, CanCompare::no, ProxyRef::no>; + + Instantiator::template call(); + Instantiator::template call(); + Instantiator::template call(); + Instantiator::template call(); + Instantiator::template call(); + Instantiator::template call(); + Instantiator::template call(); + Instantiator::template call(); +} + +int main() { + STATIC_ASSERT((run_tests(), true)); + run_tests(); + + STATIC_ASSERT((run_tests(), true)); + run_tests(); +} diff --git a/tests/std/tests/P0896R4_ranges_alg_partition_point/env.lst b/tests/std/tests/P0896R4_ranges_alg_partition_point/env.lst new file mode 100644 index 0000000000..f3ccc8613c --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_partition_point/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_partition_point/test.cpp b/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp new file mode 100644 index 0000000000..bfd99511fa --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +#define ASSERT(...) assert((__VA_ARGS__)) + +using P = pair; + +constexpr auto is_even = [](int i) { return i % 2 == 0; }; + +// Validate dangling story +STATIC_ASSERT(same_as{}, is_even)), ranges::dangling>); +STATIC_ASSERT(same_as{}, is_even)), int*>); + +struct empty_test { + template + static constexpr void call() { + // Validate empty ranges + using ranges::partition_point; + + const Range range{}; + ASSERT(partition_point(range, is_even, get_first) == range.end()); + ASSERT(partition_point(range.begin(), range.end(), is_even, get_first) == range.end()); + } +}; + +struct partition_point_test { + template + static constexpr void call() { + using ranges::partition_point; + int elements[200]; + iota(ranges::begin(elements), ranges::end(elements), 0); + + // to avoid constant expression step limits + const size_t bound = elements[0] + (is_constant_evaluated() ? 10 : ranges::size(elements)); + + for (size_t i = 0; i < bound; ++i) { + const R range{span{elements}.first(i)}; + for (size_t j = 0; j < i; ++j) { + assert(partition_point(range, [j](int x) { return x < static_cast(j); }).peek() == elements + j); + } + } + } +}; + +int main() { + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); + + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); +}