Skip to content

Commit 0218ea4

Browse files
committed
[libc++] Implement ranges::ends_with
Reviewed By: #libc, var-const Differential Revision: https://reviews.llvm.org/D150831
1 parent b76e08d commit 0218ea4

14 files changed

+631
-7
lines changed

libcxx/benchmarks/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ set(BENCHMARK_TESTS
172172
algorithms/pop_heap.bench.cpp
173173
algorithms/pstl.stable_sort.bench.cpp
174174
algorithms/push_heap.bench.cpp
175+
algorithms/ranges_ends_with.bench.cpp
175176
algorithms/ranges_make_heap.bench.cpp
176177
algorithms/ranges_make_heap_then_sort_heap.bench.cpp
177178
algorithms/ranges_pop_heap.bench.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include <algorithm>
10+
#include <benchmark/benchmark.h>
11+
#include <iterator>
12+
13+
#include "test_iterators.h"
14+
#include <vector>
15+
16+
static void bm_ends_with_contiguous_iter(benchmark::State& state) {
17+
std::vector<int> a(state.range(), 1);
18+
std::vector<int> p(state.range(), 1);
19+
20+
for (auto _ : state) {
21+
benchmark::DoNotOptimize(a);
22+
benchmark::DoNotOptimize(p);
23+
24+
auto begin1 = contiguous_iterator(a.data());
25+
auto end1 = contiguous_iterator(a.data() + a.size());
26+
auto begin2 = contiguous_iterator(p.data());
27+
auto end2 = contiguous_iterator(p.data() + p.size());
28+
29+
benchmark::DoNotOptimize(std::ranges::ends_with(begin1, end1, begin2, end2));
30+
}
31+
}
32+
BENCHMARK(bm_ends_with_contiguous_iter)->RangeMultiplier(16)->Range(16, 16 << 20);
33+
34+
static void bm_ends_with_random_iter(benchmark::State& state) {
35+
std::vector<int> a(state.range(), 1);
36+
std::vector<int> p(state.range(), 1);
37+
38+
for (auto _ : state) {
39+
benchmark::DoNotOptimize(a);
40+
benchmark::DoNotOptimize(p);
41+
42+
auto begin1 = random_access_iterator(a.begin());
43+
auto end1 = random_access_iterator(a.end());
44+
auto begin2 = random_access_iterator(p.begin());
45+
auto end2 = random_access_iterator(p.end());
46+
47+
benchmark::DoNotOptimize(std::ranges::ends_with(begin1, end1, begin2, end2));
48+
}
49+
}
50+
BENCHMARK(bm_ends_with_random_iter)->RangeMultiplier(16)->Range(16, 16 << 20);
51+
52+
static void bm_ends_with_bidirectional_iter(benchmark::State& state) {
53+
std::vector<int> a(state.range(), 1);
54+
std::vector<int> p(state.range(), 1);
55+
56+
for (auto _ : state) {
57+
benchmark::DoNotOptimize(a);
58+
benchmark::DoNotOptimize(p);
59+
60+
auto begin1 = bidirectional_iterator(a.begin());
61+
auto end1 = bidirectional_iterator(a.end());
62+
auto begin2 = bidirectional_iterator(p.begin());
63+
auto end2 = bidirectional_iterator(p.end());
64+
65+
benchmark::DoNotOptimize(std::ranges::ends_with(begin1, end1, begin2, end2));
66+
}
67+
}
68+
BENCHMARK(bm_ends_with_bidirectional_iter)->RangeMultiplier(16)->Range(16, 16 << 20);
69+
70+
static void bm_ends_with_forward_iter(benchmark::State& state) {
71+
std::vector<int> a(state.range(), 1);
72+
std::vector<int> p(state.range(), 1);
73+
74+
for (auto _ : state) {
75+
benchmark::DoNotOptimize(a);
76+
benchmark::DoNotOptimize(p);
77+
78+
auto begin1 = forward_iterator(a.begin());
79+
auto end1 = forward_iterator(a.end());
80+
auto begin2 = forward_iterator(p.begin());
81+
auto end2 = forward_iterator(p.end());
82+
83+
benchmark::DoNotOptimize(std::ranges::ends_with(begin1, end1, begin2, end2));
84+
}
85+
}
86+
BENCHMARK(bm_ends_with_forward_iter)->RangeMultiplier(16)->Range(16, 16 << 20);
87+
88+
static void bm_ends_with_forward_iter_with_size_optimization(benchmark::State& state) {
89+
std::vector<int> a(state.range(), 1);
90+
std::vector<int> p(state.range(), 1);
91+
p.push_back(2);
92+
93+
for (auto _ : state) {
94+
benchmark::DoNotOptimize(a);
95+
benchmark::DoNotOptimize(p);
96+
97+
auto begin1 = forward_iterator(a.begin());
98+
auto end1 = forward_iterator(a.end());
99+
auto begin2 = forward_iterator(p.begin());
100+
auto end2 = forward_iterator(p.end());
101+
102+
benchmark::DoNotOptimize(std::ranges::ends_with(begin1, end1, begin2, end2));
103+
}
104+
}
105+
BENCHMARK(bm_ends_with_forward_iter_with_size_optimization)->RangeMultiplier(16)->Range(16, 16 << 20);
106+
107+
BENCHMARK_MAIN();

libcxx/include/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ set(files
110110
__algorithm/ranges_copy_n.h
111111
__algorithm/ranges_count.h
112112
__algorithm/ranges_count_if.h
113+
__algorithm/ranges_ends_with.h
113114
__algorithm/ranges_equal.h
114115
__algorithm/ranges_equal_range.h
115116
__algorithm/ranges_fill.h
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef _LIBCPP___ALGORITHM_RANGES_ENDS_WITH_H
10+
#define _LIBCPP___ALGORITHM_RANGES_ENDS_WITH_H
11+
12+
#include <__algorithm/ranges_equal.h>
13+
#include <__algorithm/ranges_starts_with.h>
14+
#include <__config>
15+
#include <__functional/identity.h>
16+
#include <__functional/ranges_operations.h>
17+
#include <__functional/reference_wrapper.h>
18+
#include <__iterator/advance.h>
19+
#include <__iterator/concepts.h>
20+
#include <__iterator/distance.h>
21+
#include <__iterator/indirectly_comparable.h>
22+
#include <__iterator/reverse_iterator.h>
23+
#include <__ranges/access.h>
24+
#include <__ranges/concepts.h>
25+
#include <__utility/move.h>
26+
27+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
28+
# pragma GCC system_header
29+
#endif
30+
31+
#if _LIBCPP_STD_VER >= 23
32+
33+
_LIBCPP_BEGIN_NAMESPACE_STD
34+
35+
namespace ranges {
36+
namespace __ends_with {
37+
struct __fn {
38+
template <class _Iter1, class _Sent1, class _Iter2, class _Sent2, class _Pred, class _Proj1, class _Proj2>
39+
static _LIBCPP_HIDE_FROM_ABI constexpr bool __ends_with_fn_impl_bidirectional(
40+
_Iter1 __first1,
41+
_Sent1 __last1,
42+
_Iter2 __first2,
43+
_Sent2 __last2,
44+
_Pred& __pred,
45+
_Proj1& __proj1,
46+
_Proj2& __proj2) {
47+
auto __rbegin1 = std::make_reverse_iterator(__last1);
48+
auto __rend1 = std::make_reverse_iterator(__first1);
49+
auto __rbegin2 = std::make_reverse_iterator(__last2);
50+
auto __rend2 = std::make_reverse_iterator(__first2);
51+
return ranges::starts_with(
52+
__rbegin1, __rend1, __rbegin2, __rend2, std::ref(__pred), std::ref(__proj1), std::ref(__proj2));
53+
}
54+
55+
template <class _Iter1, class _Sent1, class _Iter2, class _Sent2, class _Pred, class _Proj1, class _Proj2>
56+
static _LIBCPP_HIDE_FROM_ABI constexpr bool __ends_with_fn_impl(
57+
_Iter1 __first1,
58+
_Sent1 __last1,
59+
_Iter2 __first2,
60+
_Sent2 __last2,
61+
_Pred& __pred,
62+
_Proj1& __proj1,
63+
_Proj2& __proj2) {
64+
if constexpr (std::bidirectional_iterator<_Sent1> && std::bidirectional_iterator<_Sent2> &&
65+
(!std::random_access_iterator<_Sent1>)&&(!std::random_access_iterator<_Sent2>)) {
66+
return __ends_with_fn_impl_bidirectional(__first1, __last1, __first2, __last2, __pred, __proj1, __proj2);
67+
68+
} else {
69+
auto __n1 = ranges::distance(__first1, __last1);
70+
auto __n2 = ranges::distance(__first2, __last2);
71+
if (__n2 == 0)
72+
return true;
73+
if (__n2 > __n1)
74+
return false;
75+
76+
return __ends_with_fn_impl_with_offset(
77+
std::move(__first1),
78+
std::move(__last1),
79+
std::move(__first2),
80+
std::move(__last2),
81+
__pred,
82+
__proj1,
83+
__proj2,
84+
__n1 - __n2);
85+
}
86+
}
87+
88+
template <class _Iter1,
89+
class _Sent1,
90+
class _Iter2,
91+
class _Sent2,
92+
class _Pred,
93+
class _Proj1,
94+
class _Proj2,
95+
class _Offset>
96+
static _LIBCPP_HIDE_FROM_ABI constexpr bool __ends_with_fn_impl_with_offset(
97+
_Iter1 __first1,
98+
_Sent1 __last1,
99+
_Iter2 __first2,
100+
_Sent2 __last2,
101+
_Pred& __pred,
102+
_Proj1& __proj1,
103+
_Proj2& __proj2,
104+
_Offset __offset) {
105+
if constexpr (std::bidirectional_iterator<_Sent1> && std::bidirectional_iterator<_Sent2> &&
106+
!std::random_access_iterator<_Sent1> && !std::random_access_iterator<_Sent2>) {
107+
return __ends_with_fn_impl_bidirectional(
108+
std::move(__first1), std::move(__last1), std::move(__first2), std::move(__last2), __pred, __proj1, __proj2);
109+
110+
} else {
111+
ranges::advance(__first1, __offset);
112+
return ranges::equal(
113+
std::move(__first1),
114+
std::move(__last1),
115+
std::move(__first2),
116+
std::move(__last2),
117+
std::ref(__pred),
118+
std::ref(__proj1),
119+
std::ref(__proj2));
120+
}
121+
}
122+
123+
template <input_iterator _Iter1,
124+
sentinel_for<_Iter1> _Sent1,
125+
input_iterator _Iter2,
126+
sentinel_for<_Iter2> _Sent2,
127+
class _Pred = ranges::equal_to,
128+
class _Proj1 = identity,
129+
class _Proj2 = identity>
130+
requires(forward_iterator<_Iter1> || sized_sentinel_for<_Sent1, _Iter1>) &&
131+
(forward_iterator<_Iter2> || sized_sentinel_for<_Sent2, _Iter2>) &&
132+
indirectly_comparable<_Iter1, _Iter2, _Pred, _Proj1, _Proj2>
133+
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(
134+
_Iter1 __first1,
135+
_Sent1 __last1,
136+
_Iter2 __first2,
137+
_Sent2 __last2,
138+
_Pred __pred = {},
139+
_Proj1 __proj1 = {},
140+
_Proj2 __proj2 = {}) const {
141+
return __ends_with_fn_impl(
142+
std::move(__first1), std::move(__last1), std::move(__first2), std::move(__last2), __pred, __proj1, __proj2);
143+
}
144+
145+
template <input_range _Range1,
146+
input_range _Range2,
147+
class _Pred = ranges::equal_to,
148+
class _Proj1 = identity,
149+
class _Proj2 = identity>
150+
requires(forward_range<_Range1> || sized_range<_Range1>) && (forward_range<_Range2> || sized_range<_Range2>) &&
151+
indirectly_comparable<iterator_t<_Range1>, iterator_t<_Range2>, _Pred, _Proj1, _Proj2>
152+
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(
153+
_Range1&& __range1, _Range2&& __range2, _Pred __pred = {}, _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const {
154+
if constexpr (sized_range<_Range1> && sized_range<_Range2>) {
155+
auto __n1 = ranges::size(__range1);
156+
auto __n2 = ranges::size(__range2);
157+
if (__n2 == 0)
158+
return true;
159+
if (__n2 > __n1)
160+
return false;
161+
auto __offset = __n1 - __n2;
162+
163+
return __ends_with_fn_impl_with_offset(
164+
ranges::begin(__range1),
165+
ranges::end(__range1),
166+
ranges::begin(__range2),
167+
ranges::end(__range2),
168+
__pred,
169+
__proj1,
170+
__proj2,
171+
__offset);
172+
173+
} else {
174+
return __ends_with_fn_impl(
175+
ranges::begin(__range1),
176+
ranges::end(__range1),
177+
ranges::begin(__range2),
178+
ranges::end(__range2),
179+
__pred,
180+
__proj1,
181+
__proj2);
182+
}
183+
}
184+
};
185+
} // namespace __ends_with
186+
187+
inline namespace __cpo {
188+
inline constexpr auto ends_with = __ends_with::__fn{};
189+
} // namespace __cpo
190+
} // namespace ranges
191+
192+
_LIBCPP_END_NAMESPACE_STD
193+
194+
#endif // _LIBCPP_STD_VER >= 23
195+
196+
#endif // _LIBCPP___ALGORITHM_RANGES_ENDS_WITH_H

libcxx/include/algorithm

+17
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,22 @@ namespace ranges {
447447
indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
448448
constexpr bool ranges::any_of(R&& r, Pred pred, Proj proj = {}); // since C++20
449449
450+
template<input_iterator I1, sentinel_for<I1> S1, input_iterator I2, sentinel_for<I2> S2,
451+
class Pred = ranges::equal_to, class Proj1 = identity, class Proj2 = identity>
452+
requires (forward_iterator<I1> || sized_sentinel_for<S1, I1>) &&
453+
(forward_iterator<I2> || sized_sentinel_for<S2, I2>) &&
454+
indirectly_comparable<I1, I2, Pred, Proj1, Proj2>
455+
constexpr bool ranges::ends_with(I1 first1, S1 last1, I2 first2, S2 last2, Pred pred = {},
456+
Proj1 proj1 = {}, Proj2 proj2 = {}); // since C++23
457+
458+
template<input_range R1, input_range R2, class Pred = ranges::equal_to, class Proj1 = identity,
459+
class Proj2 = identity>
460+
requires (forward_range<R1> || sized_range<R1>) &&
461+
(forward_range<R2> || sized_range<R2>) &&
462+
indirectly_comparable<iterator_t<R1>, iterator_t<R2>, Pred, Proj1, Proj2>
463+
constexpr bool ranges::ends_with(R1&& r1, R2&& r2, Pred pred = {},
464+
Proj1 proj1 = {}, Proj2 proj2 = {}); // since C++23
465+
450466
template<input_iterator I, sentinel_for<I> S, class Proj = identity,
451467
indirect_unary_predicate<projected<I, Proj>> Pred>
452468
constexpr bool ranges::none_of(I first, S last, Pred pred, Proj proj = {}); // since C++20
@@ -1833,6 +1849,7 @@ template <class BidirectionalIterator, class Compare>
18331849
#include <__algorithm/ranges_copy_n.h>
18341850
#include <__algorithm/ranges_count.h>
18351851
#include <__algorithm/ranges_count_if.h>
1852+
#include <__algorithm/ranges_ends_with.h>
18361853
#include <__algorithm/ranges_equal.h>
18371854
#include <__algorithm/ranges_equal_range.h>
18381855
#include <__algorithm/ranges_fill.h>

libcxx/modules/std/algorithm.inc

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ export namespace std {
154154
// [alg.starts.with], starts with
155155
using std::ranges::starts_with;
156156

157-
# if 0
158157
// [alg.ends.with], ends with
159158
using std::ranges::ends_with;
160159

160+
# if 0
161161
// [alg.fold], fold
162162
using std::ranges::fold_left;
163163
using std::ranges::fold_left_first;

libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ constexpr bool all_the_algorithms()
102102
(void)std::ranges::count_if(a, UnaryTrue(&copies)); assert(copies == 0);
103103
(void)std::ranges::copy_if(first, last, first2, UnaryTrue(&copies)); assert(copies == 0);
104104
(void)std::ranges::copy_if(a, first2, UnaryTrue(&copies)); assert(copies == 0);
105-
#if TEST_STD_VER > 20
106-
//(void)std::ranges::ends_with(first, last, first2, last2, Equal(&copies)); assert(copies == 0);
105+
#if TEST_STD_VER >= 23
106+
(void)std::ranges::ends_with(first, last, first2, last2, Equal(&copies)); assert(copies == 0);
107+
(void)std::ranges::ends_with(a, b, Equal(&copies)); assert(copies == 0);
107108
#endif
108109
(void)std::ranges::equal(first, last, first2, last2, Equal(&copies)); assert(copies == 0);
109110
(void)std::ranges::equal(a, b, Equal(&copies)); assert(copies == 0);

libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ constexpr bool all_the_algorithms()
8686
(void)std::ranges::count_if(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
8787
(void)std::ranges::copy_if(first, last, first2, UnaryTrue(), Proj(&copies)); assert(copies == 0);
8888
(void)std::ranges::copy_if(a, first2, UnaryTrue(), Proj(&copies)); assert(copies == 0);
89-
#if TEST_STD_VER > 20
90-
//(void)std::ranges::ends_with(first, last, first2, last2, Equal(), Proj(&copies), Proj(&copies)); assert(copies == 0);
89+
#if TEST_STD_VER >= 23
90+
(void)std::ranges::ends_with(first, last, first2, last2, Equal(), Proj(&copies), Proj(&copies)); assert(copies == 0);
91+
(void)std::ranges::ends_with(a, b, Equal(), Proj(&copies), Proj(&copies)); assert(copies == 0);
9192
#endif
9293
(void)std::ranges::equal(first, last, first2, last2, Equal(), Proj(&copies), Proj(&copies)); assert(copies == 0);
9394
(void)std::ranges::equal(a, b, Equal(), Proj(&copies), Proj(&copies)); assert(copies == 0);

0 commit comments

Comments
 (0)