Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions stl/inc/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class _In1, class _In2, class _Out>
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 <input_iterator _It1, sentinel_for<_It1> _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 <input_range _Rng1, input_range _Rng2, weakly_incrementable _Out, class _Pr = ranges::less,
class _Pj1 = identity, class _Pj2 = identity>
requires mergeable<iterator_t<_Rng1>, iterator_t<_Rng2>, _Out, _Pr, _Pj1, _Pj2>
constexpr merge_result<borrowed_iterator_t<_Rng1>, 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 <class _It1, class _Se1, class _It2, class _Se2, class _Out, class _Pr, class _Pj1, class _Pj2>
_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
Expand Down
1 change: 1 addition & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_merge/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
154 changes: 154 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_merge/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <cassert>
#include <concepts>
#include <ranges>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;
using P = pair<int, int>;

// Validate that merge_result aliases in_in_out_result
STATIC_ASSERT(same_as<ranges::merge_result<int, void*, double>, ranges::in_in_out_result<int, void*, double>>);

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::merge(borrowed<false>{}, borrowed<false>{}, nullptr_to<int>)),
ranges::merge_result<ranges::dangling, ranges::dangling, int*>>);
STATIC_ASSERT(same_as<decltype(ranges::merge(borrowed<false>{}, borrowed<true>{}, nullptr_to<int>)),
ranges::merge_result<ranges::dangling, int*, int*>>);
STATIC_ASSERT(same_as<decltype(ranges::merge(borrowed<true>{}, borrowed<false>{}, nullptr_to<int>)),
ranges::merge_result<int*, ranges::dangling, int*>>);
STATIC_ASSERT(same_as<decltype(ranges::merge(borrowed<true>{}, borrowed<true>{}, nullptr_to<int>)),
ranges::merge_result<int*, int*, int*>>);

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 <ranges::input_range R1, ranges::input_range R2, weakly_incrementable O>
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<merge_result<iterator_t<R1>, iterator_t<R2>, 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<merge_result<iterator_t<R1>, iterator_t<R2>, 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<merge_result<iterator_t<R1>, iterator_t<R2>, 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<merge_result<iterator_t<R1>, iterator_t<R2>, 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 <class Continuation>
struct generate_readable_ranges {
template <class... Args>
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<Args...,
range<input, const P, Sized::no, CanDifference::no, Common::no, CanCompare::no, ProxyRef::no>>();
Continuation::template call<Args...,
range<input, const P, Sized::no, CanDifference::no, Common::no, CanCompare::no, ProxyRef::yes>>();
}
};

template <class Continuation>
struct generate_writable_iterators {
template <class... Args>
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<Args..., iterator<output, P, CanDifference::no, CanCompare::no, ProxyRef::no>>();
Continuation::template call<Args..., iterator<output, P, CanDifference::no, CanCompare::no, ProxyRef::yes>>();

Continuation::template call<Args..., iterator<input, P, CanDifference::no, CanCompare::no, ProxyRef::no>>();
Continuation::template call<Args..., iterator<input, P, CanDifference::no, CanCompare::no, ProxyRef::yes>>();
}
};

constexpr void run_tests() {
generate_readable_ranges<generate_readable_ranges<generate_writable_iterators<instantiator>>>::call();
}

int main() {
STATIC_ASSERT((run_tests(), true));
run_tests();
}