diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 9f5ea0f97cf..eaa9d34fe1a 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -4334,6 +4334,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 @@ -4507,6 +4573,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 8b46ac33bfa..92e2fc00bfa 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -282,6 +282,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