Skip to content

Commit 5d00b60

Browse files
authored
Implement ranges::merge (#1101)
1 parent 0eb754d commit 5d00b60

File tree

4 files changed

+243
-0
lines changed

4 files changed

+243
-0
lines changed

stl/inc/algorithm

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7089,6 +7089,90 @@ _FwdIt3 merge(_ExPo&&, _FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2
70897089
_REQUIRE_PARALLEL_ITERATOR(_FwdIt3);
70907090
return _STD merge(_First1, _Last1, _First2, _Last2, _Dest);
70917091
}
7092+
7093+
#ifdef __cpp_lib_concepts
7094+
namespace ranges {
7095+
// ALIAS TEMPLATE merge_result
7096+
template <class _In1, class _In2, class _Out>
7097+
using merge_result = in_in_out_result<_In1, _In2, _Out>;
7098+
7099+
// VARIABLE ranges::merge
7100+
class _Merge_fn : private _Not_quite_object {
7101+
public:
7102+
using _Not_quite_object::_Not_quite_object;
7103+
7104+
// clang-format off
7105+
template <input_iterator _It1, sentinel_for<_It1> _Se1, input_iterator _It2, sentinel_for<_It2> _Se2,
7106+
weakly_incrementable _Out, class _Pr = ranges::less, class _Pj1 = identity, class _Pj2 = identity>
7107+
requires mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2>
7108+
constexpr merge_result<_It1, _It2, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2,
7109+
_Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const {
7110+
// clang-format on
7111+
_Adl_verify_range(_First1, _Last1);
7112+
_Adl_verify_range(_First2, _Last2);
7113+
auto _UResult = _Merge_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)),
7114+
_Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2)), _STD move(_Result),
7115+
_Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2));
7116+
_Seek_wrapped(_First1, _STD move(_UResult.in1));
7117+
_Seek_wrapped(_First2, _STD move(_UResult.in2));
7118+
return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)};
7119+
}
7120+
7121+
// clang-format off
7122+
template <input_range _Rng1, input_range _Rng2, weakly_incrementable _Out, class _Pr = ranges::less,
7123+
class _Pj1 = identity, class _Pj2 = identity>
7124+
requires mergeable<iterator_t<_Rng1>, iterator_t<_Rng2>, _Out, _Pr, _Pj1, _Pj2>
7125+
constexpr merge_result<borrowed_iterator_t<_Rng1>, borrowed_iterator_t<_Rng2>, _Out> operator()(
7126+
_Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const {
7127+
// clang-format on
7128+
auto _First1 = _RANGES begin(_Range1);
7129+
auto _First2 = _RANGES begin(_Range2);
7130+
auto _UResult =
7131+
_Merge_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), _Get_unwrapped(_STD move(_First2)),
7132+
_Uend(_Range2), _STD move(_Result), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2));
7133+
_Seek_wrapped(_First1, _STD move(_UResult.in1));
7134+
_Seek_wrapped(_First2, _STD move(_UResult.in2));
7135+
return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)};
7136+
}
7137+
7138+
private:
7139+
template <class _It1, class _Se1, class _It2, class _Se2, class _Out, class _Pr, class _Pj1, class _Pj2>
7140+
_NODISCARD static constexpr merge_result<_It1, _It2, _Out> _Merge_unchecked(_It1 _First1, const _Se1 _Last1,
7141+
_It2 _First2, const _Se2 _Last2, _Out _Result, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) {
7142+
_STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>);
7143+
_STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>);
7144+
_STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>);
7145+
_STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>);
7146+
_STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>);
7147+
_STL_INTERNAL_STATIC_ASSERT(mergeable<_It1, _It2, _Out, _Pr, _Pj1, _Pj2>);
7148+
7149+
for (;; ++_Result) {
7150+
if (_First1 == _Last1) {
7151+
auto _Copy_result =
7152+
_RANGES _Copy_unchecked(_STD move(_First2), _STD move(_Last2), _STD move(_Result));
7153+
return {_STD move(_First1), _STD move(_Copy_result.in), _STD move(_Copy_result.out)};
7154+
}
7155+
7156+
if (_First2 == _Last2) {
7157+
auto _Copy_result =
7158+
_RANGES _Copy_unchecked(_STD move(_First1), _STD move(_Last1), _STD move(_Result));
7159+
return {_STD move(_Copy_result.in), _STD move(_First2), _STD move(_Copy_result.out)};
7160+
}
7161+
7162+
if (_STD invoke(_Pred, _STD invoke(_Proj2, *_First2), _STD invoke(_Proj1, *_First1))) {
7163+
*_Result = *_First2;
7164+
++_First2;
7165+
} else {
7166+
*_Result = *_First1;
7167+
++_First1;
7168+
}
7169+
}
7170+
}
7171+
};
7172+
7173+
inline constexpr _Merge_fn merge{_Not_quite_object::_Construct_tag{}};
7174+
} // namespace ranges
7175+
#endif // __cpp_lib_concepts
70927176
#endif // _HAS_CXX17
70937177

70947178
// FUNCTION TEMPLATE inplace_merge

tests/std/test.lst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ tests\P0896R4_ranges_alg_heap
265265
tests\P0896R4_ranges_alg_includes
266266
tests\P0896R4_ranges_alg_is_permutation
267267
tests\P0896R4_ranges_alg_is_sorted
268+
tests\P0896R4_ranges_alg_merge
268269
tests\P0896R4_ranges_alg_minmax
269270
tests\P0896R4_ranges_alg_mismatch
270271
tests\P0896R4_ranges_alg_move
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
RUNALL_INCLUDE ..\concepts_matrix.lst
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include <algorithm>
5+
#include <cassert>
6+
#include <concepts>
7+
#include <ranges>
8+
#include <utility>
9+
10+
#include <range_algorithm_support.hpp>
11+
12+
using namespace std;
13+
using P = pair<int, int>;
14+
15+
// Validate that merge_result aliases in_in_out_result
16+
STATIC_ASSERT(same_as<ranges::merge_result<int, void*, double>, ranges::in_in_out_result<int, void*, double>>);
17+
18+
// Validate dangling story
19+
STATIC_ASSERT(same_as<decltype(ranges::merge(borrowed<false>{}, borrowed<false>{}, nullptr_to<int>)),
20+
ranges::merge_result<ranges::dangling, ranges::dangling, int*>>);
21+
STATIC_ASSERT(same_as<decltype(ranges::merge(borrowed<false>{}, borrowed<true>{}, nullptr_to<int>)),
22+
ranges::merge_result<ranges::dangling, int*, int*>>);
23+
STATIC_ASSERT(same_as<decltype(ranges::merge(borrowed<true>{}, borrowed<false>{}, nullptr_to<int>)),
24+
ranges::merge_result<int*, ranges::dangling, int*>>);
25+
STATIC_ASSERT(same_as<decltype(ranges::merge(borrowed<true>{}, borrowed<true>{}, nullptr_to<int>)),
26+
ranges::merge_result<int*, int*, int*>>);
27+
28+
struct instantiator {
29+
static constexpr P elements1[] = {{0, 10}, {0, 11}, {0, 12}, {1, 10}, {1, 11}, {3, 10}};
30+
static constexpr P elements2[] = {{13, 0}, {14, 0}, {10, 2}, {11, 3}, {12, 3}};
31+
static constexpr P expected[] = {
32+
{0, 10}, {0, 11}, {0, 12}, {13, 0}, {14, 0}, {1, 10}, {1, 11}, {10, 2}, {3, 10}, {11, 3}, {12, 3}};
33+
34+
static constexpr auto counting_compare(size_t& counter) {
35+
return [&counter](auto&& x, auto&& y) {
36+
++counter;
37+
return ranges::less{}(x, y);
38+
};
39+
}
40+
41+
template <ranges::input_range R1, ranges::input_range R2, weakly_incrementable O>
42+
static constexpr void call() {
43+
using ranges::merge, ranges::merge_result, ranges::end, ranges::equal, ranges::iterator_t, ranges::size;
44+
45+
{ // Validate range overload
46+
P output[size(expected)]{};
47+
R1 range1{elements1};
48+
R2 range2{elements2};
49+
size_t counter = 0;
50+
51+
const same_as<merge_result<iterator_t<R1>, iterator_t<R2>, O>> auto result =
52+
merge(range1, range2, O{output}, counting_compare(counter), get_first, get_second);
53+
assert(result.in1 == range1.end());
54+
assert(result.in2 == range2.end());
55+
assert(result.out.peek() == end(output));
56+
assert(equal(output, expected));
57+
assert(counter <= size(elements1) + size(elements2) - 1);
58+
}
59+
{ // Validate iterator overload
60+
P output[size(expected)]{};
61+
R1 range1{elements1};
62+
R2 range2{elements2};
63+
size_t counter = 0;
64+
65+
const same_as<merge_result<iterator_t<R1>, iterator_t<R2>, O>> auto result =
66+
merge(range1.begin(), range1.end(), range2.begin(), range2.end(), O{output}, counting_compare(counter),
67+
get_first, get_second);
68+
assert(result.in1 == range1.end());
69+
assert(result.in2 == range2.end());
70+
assert(result.out.peek() == end(output));
71+
assert(equal(output, expected));
72+
assert(counter <= size(elements1) + size(elements2) - 1);
73+
}
74+
75+
{ // Validate range overload, empty range1
76+
P output[size(elements2)]{};
77+
R1 range1{};
78+
R2 range2{elements2};
79+
size_t counter = 0;
80+
81+
const same_as<merge_result<iterator_t<R1>, iterator_t<R2>, O>> auto result =
82+
merge(range1, range2, O{output}, counting_compare(counter), get_first, get_second);
83+
assert(result.in1 == range1.end());
84+
assert(result.in2 == range2.end());
85+
assert(result.out.peek() == end(output));
86+
assert(equal(output, elements2));
87+
assert(counter == 0);
88+
}
89+
{ // Validate iterator overload, empty range2
90+
P output[size(elements1)]{};
91+
R1 range1{elements1};
92+
R2 range2{};
93+
size_t counter = 0;
94+
95+
const same_as<merge_result<iterator_t<R1>, iterator_t<R2>, O>> auto result =
96+
merge(range1.begin(), range1.end(), range2.begin(), range2.end(), O{output}, counting_compare(counter),
97+
get_first, get_second);
98+
assert(result.in1 == range1.end());
99+
assert(result.in2 == range2.end());
100+
assert(result.out.peek() == end(output));
101+
assert(equal(output, elements1));
102+
assert(counter == 0);
103+
}
104+
}
105+
};
106+
107+
template <class Continuation>
108+
struct generate_readable_ranges {
109+
template <class... Args>
110+
static constexpr void call() {
111+
using namespace test;
112+
using test::range;
113+
114+
// The algorithm is completely oblivious to:
115+
// * categories stronger than input
116+
// * whether the end sentinel is an iterator
117+
// * size information
118+
// * iterator and/or sentinel differencing
119+
// so let's vary proxyness for coverage and call it good.
120+
121+
Continuation::template call<Args...,
122+
range<input, const P, Sized::no, CanDifference::no, Common::no, CanCompare::no, ProxyRef::no>>();
123+
Continuation::template call<Args...,
124+
range<input, const P, Sized::no, CanDifference::no, Common::no, CanCompare::no, ProxyRef::yes>>();
125+
}
126+
};
127+
128+
template <class Continuation>
129+
struct generate_writable_iterators {
130+
template <class... Args>
131+
static constexpr void call() {
132+
using namespace test;
133+
using test::iterator;
134+
135+
// The algorithm is completely oblivious to all properties except for proxyness,
136+
// so again we'll vary that property, and we'll also get coverage from input iterators to ensure the algorithm
137+
// doesn't inadvertently depend on the output_iterator-only `*i++ = meow` expression.
138+
139+
Continuation::template call<Args..., iterator<output, P, CanDifference::no, CanCompare::no, ProxyRef::no>>();
140+
Continuation::template call<Args..., iterator<output, P, CanDifference::no, CanCompare::no, ProxyRef::yes>>();
141+
142+
Continuation::template call<Args..., iterator<input, P, CanDifference::no, CanCompare::no, ProxyRef::no>>();
143+
Continuation::template call<Args..., iterator<input, P, CanDifference::no, CanCompare::no, ProxyRef::yes>>();
144+
}
145+
};
146+
147+
constexpr void run_tests() {
148+
generate_readable_ranges<generate_readable_ranges<generate_writable_iterators<instantiator>>>::call();
149+
}
150+
151+
int main() {
152+
STATIC_ASSERT((run_tests(), true));
153+
run_tests();
154+
}

0 commit comments

Comments
 (0)