diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 27099d60c70..43b92a84638 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -6807,6 +6807,90 @@ _FwdIt3 merge(_ExPo&&, _FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _REQUIRE_PARALLEL_ITERATOR(_FwdIt3); return _STD merge(_First1, _Last1, _First2, _Last2, _Dest); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE merge_result + template + using merge_result = in_in_out_result<_In1, _In2, _Out>; + + // VARIABLE ranges::merge + class _Merge_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 merge_result<_It1, _It2, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, + _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + // clang-format on + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UResult = _Merge_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(_Pred), _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 off + template + requires mergeable, iterator_t<_Rng2>, _Out, _Pr, _Pj1, _Pj2> + constexpr merge_result, borrowed_iterator_t<_Rng2>, _Out> operator()( + _Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + // clang-format on + auto _First1 = _RANGES begin(_Range1); + auto _First2 = _RANGES begin(_Range2); + auto _UResult = + _Merge_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), _Get_unwrapped(_STD move(_First2)), + _Uend(_Range2), _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)); + return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)}; + } + + private: + template + _NODISCARD static constexpr merge_result<_It1, _It2, _Out> _Merge_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 (;; ++_Result) { + if (_First1 == _Last1) { + auto _Copy_result = + _RANGES _Copy_unchecked(_STD move(_First2), _STD move(_Last2), _STD move(_Result)); + return {_STD move(_First1), _STD move(_Copy_result.in), _STD move(_Copy_result.out)}; + } + + if (_First2 == _Last2) { + auto _Copy_result = + _RANGES _Copy_unchecked(_STD move(_First1), _STD move(_Last1), _STD move(_Result)); + return {_STD move(_Copy_result.in), _STD move(_First2), _STD move(_Copy_result.out)}; + } + + if (_STD invoke(_Pred, _STD invoke(_Proj2, *_First2), _STD invoke(_Proj1, *_First1))) { + *_Result = *_First2; + ++_First2; + } else { + *_Result = *_First1; + ++_First1; + } + } + } + }; + + inline constexpr _Merge_fn merge{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE inplace_merge diff --git a/tests/std/test.lst b/tests/std/test.lst index dc0c7187509..04fb39ca2bb 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -264,6 +264,7 @@ 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_merge tests\P0896R4_ranges_alg_minmax tests\P0896R4_ranges_alg_mismatch tests\P0896R4_ranges_alg_move diff --git a/tests/std/tests/P0896R4_ranges_alg_merge/env.lst b/tests/std/tests/P0896R4_ranges_alg_merge/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_merge/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_merge/test.cpp b/tests/std/tests/P0896R4_ranges_alg_merge/test.cpp new file mode 100644 index 00000000000..d9475353bb2 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_merge/test.cpp @@ -0,0 +1,154 @@ +// 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 merge_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::merge_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to)), + ranges::merge_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to)), + ranges::merge_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to)), + ranges::merge_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}, {13, 0}, {14, 0}, {1, 10}, {1, 11}, {10, 2}, {3, 10}, {11, 3}, {12, 3}}; + + static constexpr auto counting_compare(size_t& counter) { + return [&counter](auto&& x, auto&& y) { + ++counter; + return ranges::less{}(x, y); + }; + } + + template + static constexpr void call() { + using ranges::merge, ranges::merge_result, ranges::end, ranges::equal, ranges::iterator_t, ranges::size; + + { // Validate range overload + P output[size(expected)]{}; + R1 range1{elements1}; + R2 range2{elements2}; + size_t counter = 0; + + const same_as, iterator_t, O>> auto result = + merge(range1, range2, O{output}, counting_compare(counter), get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == end(output)); + assert(equal(output, expected)); + assert(counter <= size(elements1) + size(elements2) - 1); + } + { // Validate iterator overload + P output[size(expected)]{}; + R1 range1{elements1}; + R2 range2{elements2}; + size_t counter = 0; + + const same_as, iterator_t, O>> auto result = + merge(range1.begin(), range1.end(), range2.begin(), range2.end(), O{output}, counting_compare(counter), + get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == end(output)); + assert(equal(output, expected)); + assert(counter <= size(elements1) + size(elements2) - 1); + } + + { // Validate range overload, empty range1 + P output[size(elements2)]{}; + R1 range1{}; + R2 range2{elements2}; + size_t counter = 0; + + const same_as, iterator_t, O>> auto result = + merge(range1, range2, O{output}, counting_compare(counter), get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == end(output)); + assert(equal(output, elements2)); + assert(counter == 0); + } + { // Validate iterator overload, empty range2 + P output[size(elements1)]{}; + R1 range1{elements1}; + R2 range2{}; + size_t counter = 0; + + const same_as, iterator_t, O>> auto result = + merge(range1.begin(), range1.end(), range2.begin(), range2.end(), O{output}, counting_compare(counter), + get_first, get_second); + assert(result.in1 == range1.end()); + assert(result.in2 == range2.end()); + assert(result.out.peek() == end(output)); + assert(equal(output, elements1)); + assert(counter == 0); + } + } +}; + +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(); +}