From e574666c420a7f0ae8d32d8562bacb9f547b5b7a Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Sat, 27 Jun 2020 17:38:34 -0700 Subject: [PATCH 1/4] Implement most partition family algorithms Includes `ranges::is_partitioned`, `ranges::partition`, `ranges::partition_copy`, and `ranges::partition_point`. --- stl/inc/algorithm | 308 ++++++++++++++++++ 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 | 137 ++++++++ .../env.lst | 4 + .../test.cpp | 64 ++++ 8 files changed, 639 insertions(+) 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 59db8eb1959..2c1f9acd787 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1715,6 +1715,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<_It, _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> + _NODISCARD 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> + _NODISCARD 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 @@ -1749,6 +1820,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) { @@ -1774,6 +1898,107 @@ _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); + if constexpr (sized_sentinel_for<_Se, _It>) { + auto _UFirst = _Get_unwrapped(_STD move(_First)); + const auto _Length = _Get_unwrapped(_STD move(_Last)) - _UFirst; + _UFirst = _Partition_point_with_length(_STD move(_UFirst), _Length, _Pass_fn(_Pred), _Pass_fn(_Proj)); + _Seek_wrapped(_First, _STD move(_UFirst)); + return _First; + } else { + auto _UFirst = _Partition_point_without_length(_Get_unwrapped(_STD move(_First)), + _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_with_length(_Ubegin(_Range), _Length, _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _Rewrap_iterator(_Range, _STD move(_UFirst)); + } else { + auto _UFirst = + _Partition_point_without_length(_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_with_length( + _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_without_length( + _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_with_length(_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 @@ -3958,6 +4183,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> + _NODISCARD 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> + _NODISCARD 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 diff --git a/tests/std/test.lst b/tests/std/test.lst index 6358d0afa94..078e26d84c1 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -261,6 +261,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_search tests\P0896R4_ranges_alg_search_n tests\P0896R4_ranges_alg_swap_ranges 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 00000000000..f3ccc8613c6 --- /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 00000000000..2dae09ffc5d --- /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 (!std::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 00000000000..f3ccc8613c6 --- /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 00000000000..b4f9c4cf1ea --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp @@ -0,0 +1,137 @@ +// 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 +template +inline constexpr T* nullptr_to = nullptr; + +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::partial_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 strong-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 or 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 00000000000..f3ccc8613c6 --- /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 00000000000..16d08f4e393 --- /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 + +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; + constexpr int N = 200; + int elements[N]; + iota(elements, elements + N, 0); + + // to avoid constant expression step limits + int bound = is_constant_evaluated() ? 10 : N; + + for (int i = 0; i < bound; ++i) { + const R range{span{elements, elements + i}}; + for (int j = 0; j < i; ++j) { + assert(partition_point(range, [j](int x) { return x < j; }).peek() == elements + j); + } + } + } +}; + +int main() { + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); + + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); +} From a9677638f77a3d2ea8ba78795500b771473c2414 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 7 Jul 2020 13:34:43 -0700 Subject: [PATCH 2/4] Review comment WIP --- stl/inc/algorithm | 62 +++++++++---------- .../test.cpp | 7 +-- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 2c1f9acd787..896faf2ba39 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 { @@ -1719,8 +1719,8 @@ pair<_FwdIt2, _FwdIt3> partition_copy(_ExPo&&, _FwdIt1 _First, _FwdIt1 _Last, _F #ifdef __cpp_lib_concepts namespace ranges { // ALIAS TEMPLATE partition_copy_result - template - using partition_copy_result = in_out_out_result<_It, _Out1, _Out2>; + template + using partition_copy_result = in_out_out_result<_In, _Out1, _Out2>; // VARIABLE ranges::partition_copy class _Partition_copy_fn : private _Not_quite_object { @@ -1731,7 +1731,7 @@ namespace ranges { template _Se, weakly_incrementable _Out1, weakly_incrementable _Out2, class _Pj = identity, indirect_unary_predicate> _Pr> requires indirectly_copyable<_It, _Out1> && indirectly_copyable<_It, _Out2> - _NODISCARD constexpr partition_copy_result<_It, _Out1, _Out2> operator()( + 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)), @@ -1746,7 +1746,7 @@ namespace ranges { template , _Pj>> _Pr> requires indirectly_copyable, _Out1> && indirectly_copyable, _Out2> - _NODISCARD constexpr partition_copy_result, _Out1, _Out2> operator()( + 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), @@ -1909,18 +1909,17 @@ 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)); if constexpr (sized_sentinel_for<_Se, _It>) { - auto _UFirst = _Get_unwrapped(_STD move(_First)); const auto _Length = _Get_unwrapped(_STD move(_Last)) - _UFirst; - _UFirst = _Partition_point_with_length(_STD move(_UFirst), _Length, _Pass_fn(_Pred), _Pass_fn(_Proj)); - _Seek_wrapped(_First, _STD move(_UFirst)); - return _First; + _UFirst = _Partition_point_n_unchecked(_STD move(_UFirst), _Length, _Pass_fn(_Pred), _Pass_fn(_Proj)); } else { - auto _UFirst = _Partition_point_without_length(_Get_unwrapped(_STD move(_First)), - _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); - _Seek_wrapped(_First, _STD move(_UFirst)); - return _First; + _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 operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { if constexpr (sized_range<_Rng>) { const auto _Length = _RANGES distance(_Range); - auto _UFirst = _Partition_point_with_length(_Ubegin(_Range), _Length, _Pass_fn(_Pred), _Pass_fn(_Proj)); + 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_without_length(_Ubegin(_Range), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + _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_with_length( + _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>>); @@ -1962,8 +1961,7 @@ namespace ranges { } template - _NODISCARD static constexpr _It _Partition_point_without_length( - _It _First, const _Se _Last, _Pr _Pred, _Pj _Proj) { + _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>>); @@ -1991,7 +1989,7 @@ namespace ranges { } } - return _Partition_point_with_length(_STD move(_First), _Skip, _Pred, _Proj); + return _Partition_point_n_unchecked(_STD move(_First), _Skip, _Pred, _Proj); } }; @@ -4193,7 +4191,7 @@ namespace ranges { template _Se, class _Pj = identity, indirect_unary_predicate> _Pr> - _NODISCARD constexpr subrange<_It> operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { + 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)); @@ -4203,7 +4201,7 @@ namespace ranges { // clang-format off template , _Pj>> _Pr> requires permutable> - _NODISCARD constexpr borrowed_subrange_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { + 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)); } @@ -6749,8 +6747,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/tests/P0896R4_ranges_alg_partition_point/test.cpp b/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp index 16d08f4e393..2b4ff6b0c3b 100644 --- a/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp @@ -39,12 +39,11 @@ struct partition_point_test { template static constexpr void call() { using ranges::partition_point; - constexpr int N = 200; - int elements[N]; - iota(elements, elements + N, 0); + int elements[200]; + iota(ranges::begin(elements), ranges::end(elements), 0); // to avoid constant expression step limits - int bound = is_constant_evaluated() ? 10 : N; + int bound = is_constant_evaluated() ? 10 : static_cast(ranges::size(elements)); for (int i = 0; i < bound; ++i) { const R range{span{elements, elements + i}}; From 8eac2bec0591a86d8cfc4fb3aa22db66dcd4e280 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Wed, 8 Jul 2020 21:44:36 -0700 Subject: [PATCH 3/4] WIP --- .../tests/P0896R4_ranges_alg_partition_point/test.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp b/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp index 2b4ff6b0c3b..bfd99511fae 100644 --- a/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -43,12 +44,12 @@ struct partition_point_test { iota(ranges::begin(elements), ranges::end(elements), 0); // to avoid constant expression step limits - int bound = is_constant_evaluated() ? 10 : static_cast(ranges::size(elements)); + const size_t bound = elements[0] + (is_constant_evaluated() ? 10 : ranges::size(elements)); - for (int i = 0; i < bound; ++i) { - const R range{span{elements, elements + i}}; - for (int j = 0; j < i; ++j) { - assert(partition_point(range, [j](int x) { return x < j; }).peek() == elements + j); + 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); } } } From d59e0bc957ac35d746fc52bf1a0fd24e01e07d88 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Wed, 8 Jul 2020 23:01:07 -0700 Subject: [PATCH 4/4] Review comments --- tests/std/include/range_algorithm_support.hpp | 3 +++ tests/std/tests/P0896R4_ranges_alg_partition/test.cpp | 2 +- .../std/tests/P0896R4_ranges_alg_partition_copy/test.cpp | 9 +++------ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index b02043749a9..d749c8ceb9a 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/tests/P0896R4_ranges_alg_partition/test.cpp b/tests/std/tests/P0896R4_ranges_alg_partition/test.cpp index 2dae09ffc5d..785d6ef20d8 100644 --- a/tests/std/tests/P0896R4_ranges_alg_partition/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_partition/test.cpp @@ -75,7 +75,7 @@ struct partition_test { } #if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163 - if (!std::is_constant_evaluated()) + if (!is_constant_evaluated()) #endif // TRANSITION, VSO-938163 { if constexpr (ranges::forward_range) { diff --git a/tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp index b4f9c4cf1ea..12d3d4cbd86 100644 --- a/tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp @@ -24,9 +24,6 @@ STATIC_ASSERT( same_as, ranges::in_out_out_result>); // Validate dangling story -template -inline constexpr T* nullptr_to = nullptr; - STATIC_ASSERT(same_as{}, nullptr_to, nullptr_to, is_even)), ranges::partition_copy_result>); STATIC_ASSERT(same_as{}, nullptr_to, nullptr_to, is_even)), @@ -92,11 +89,11 @@ struct partition_copy_test { 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::partial_copy. What combinations of + // 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 strong-than-input categories, + // * 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 @@ -104,7 +101,7 @@ constexpr void run_tests() { // 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 or which are treated equivalently, the algorithm is similarly oblivious to properties other + // 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.