Skip to content

Commit a1cd8ff

Browse files
committed
* Fix C++03 compatibility issues.
* Fix tests I had broken. * More tweaks and better comments.
1 parent bb872e0 commit a1cd8ff

File tree

6 files changed

+76
-36
lines changed

6 files changed

+76
-36
lines changed

libcxx/include/__algorithm/iterator_operations.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ struct _IterOps<_ClassicAlgPolicy> {
9898
template <class _InputIter, class _Distance>
9999
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 static _Distance
100100
__advance(_InputIter& __iter, _Distance __count, const _InputIter& __sentinel, input_iterator_tag) {
101-
_Distance __dist{};
101+
_Distance __dist = _Distance();
102102
for (; __dist < __count && __iter != __sentinel; ++__dist)
103103
++__iter;
104104
return __count - __dist;
@@ -108,7 +108,7 @@ struct _IterOps<_ClassicAlgPolicy> {
108108
template <class _BiDirIter, class _Distance>
109109
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 static _Distance
110110
__advance(_BiDirIter& __iter, _Distance __count, const _BiDirIter& __sentinel, bidirectional_iterator_tag) {
111-
_Distance __dist{};
111+
_Distance __dist = _Distance();
112112
if (__count >= 0)
113113
for (; __dist < __count && __iter != __sentinel; ++__dist)
114114
++__iter;
@@ -120,7 +120,7 @@ struct _IterOps<_ClassicAlgPolicy> {
120120

121121
// advance with sentinel, a la std::ranges::advance -- RandomIterator specialization
122122
template <class _RandIter, class _Distance>
123-
_LIBCPP_HIDE_FROM_ABI constexpr static _Distance
123+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR static _Distance
124124
__advance(_RandIter& __iter, _Distance __count, const _RandIter& __sentinel, random_access_iterator_tag) {
125125
auto __dist = _IterOps::distance(__iter, __sentinel);
126126
_LIBCPP_ASSERT_UNCATEGORIZED(

libcxx/include/__algorithm/lower_bound.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Iter
5050

5151
// One-sided binary search, aka meta binary search, has been in the public domain for decades, and has the general
5252
// advantage of being Ω(1) rather than the classic algorithm's Ω(log(n)), with the downside of executing at most
53-
// 2*(log(n)-1) comparisons vs the classic algorithm's exact log(n). There are two scenarios in which it really shines:
53+
// 2*log(n) comparisons vs the classic algorithm's exact log(n). There are two scenarios in which it really shines:
5454
// the first one is when operating over non-random iterators, because the classic algorithm requires knowing the
5555
// container's size upfront, which adds Ω(n) iterator increments to the complexity. The second one is when you're
5656
// traversing the container in order, trying to fast-forward to the next value: in that case, the classic algorithm
@@ -63,11 +63,9 @@ __lower_bound_onesided(_Iter __first, _Sent __last, const _Type& __value, _Comp&
6363
// __iterator_category<_Iter>>::value,
6464
// "lower_bound() is a multipass algorithm and requires forward iterator or better");
6565

66-
// split the step 0 scenario: this allows us to match worst-case complexity
67-
// when replacing linear search
66+
// step = 0, ensuring we can always short-circuit when distance is 1 later on
6867
if (__first == __last || !std::__invoke(__comp, std::__invoke(__proj, *__first), __value))
6968
return __first;
70-
++__first;
7169

7270
using _Distance = typename iterator_traits<_Iter>::difference_type;
7371
for (_Distance __step = 1; __first != __last; __step <<= 1) {
@@ -76,10 +74,14 @@ __lower_bound_onesided(_Iter __first, _Sent __last, const _Type& __value, _Comp&
7674
// once we reach the last range where needle can be we must start
7775
// looking inwards, bisecting that range
7876
if (__it == __last || !std::__invoke(__comp, std::__invoke(__proj, *__it), __value)) {
77+
// we've already checked the previous value and it was less, we can save
78+
// one comparison by skipping bisection
79+
if (__dist == 1)
80+
return __it;
7981
return std::__lower_bound_bisecting<_AlgPolicy>(__first, __value, __dist, __comp, __proj);
8082
}
8183
// range not found, move forward!
82-
__first = std::move(__it);
84+
__first = __it;
8385
}
8486
return __first;
8587
}

libcxx/include/__algorithm/set_intersection.h

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <__type_traits/is_same.h>
2121
#include <__utility/exchange.h>
2222
#include <__utility/move.h>
23+
#include <__utility/swap.h>
2324

2425
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
2526
# pragma GCC system_header
@@ -50,8 +51,7 @@ struct _LIBCPP_NODISCARD_EXT __set_intersector {
5051
const _Sent2& __last2_;
5152
_OutIter& __result_;
5253
_Compare& __comp_;
53-
static constexpr auto __proj_ = std::__identity();
54-
bool __prev_advanced_ = true;
54+
bool __prev_advanced_ = true;
5555

5656
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersector(
5757
_InIter1& __first1, _Sent1& __last1, _InIter2& __first2, _Sent2& __last2, _OutIter& __result, _Compare& __comp)
@@ -64,7 +64,7 @@ struct _LIBCPP_NODISCARD_EXT __set_intersector {
6464

6565
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
6666
__set_intersection_result<_InIter1, _InIter2, _OutIter>
67-
operator()() && {
67+
operator()() {
6868
while (__first2_ != __last2_) {
6969
__advance1_and_maybe_add_result();
7070
if (__first1_ == __last1_)
@@ -85,9 +85,27 @@ struct _LIBCPP_NODISCARD_EXT __set_intersector {
8585
template <class _Iter, class _Sent, class _Value>
8686
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void
8787
__advance_and_maybe_add_result(_Iter& __iter, const _Sent& __sentinel, const _Value& __value) {
88-
// use one-sided lower bound for improved algorithmic complexity bounds
89-
const auto __tmp = std::move(__iter);
90-
__iter = std::__lower_bound_onesided<_AlgPolicy>(__iter, __sentinel, __value, __comp_, __proj_);
88+
static _LIBCPP_CONSTEXPR std::__identity __proj;
89+
// use one-sided binary search for improved algorithmic complexity bounds
90+
// understanding how we can use binary search and still respect complexity
91+
// guarantees is _not_ straightforward, so let me explain: the guarantee
92+
// is "at most 2*(N+M)-1 comparisons", and one-sided binary search will
93+
// necessarily overshoot depending on the position of the needle in the
94+
// haystack -- for instance, if we're searching for 3 in (1, 2, 3, 4),
95+
// we'll check if 3<1, then 3<2, then 3<4, and, finally, 3<3, for a total of
96+
// 4 comparisons, when linear search would have yielded 3. However,
97+
// because we won't need to perform the intervening reciprocal comparisons
98+
// (ie 1<3, 2<3, 4<3), that extra comparison doesn't run afoul of the
99+
// guarantee. Additionally, this type of scenario can only happen for match
100+
// distances of up to 5 elements, because 2*log2(8) is 6, and we'll still
101+
// be worse-off at position 5 of an 8-element set. From then onwards
102+
// these scenarios can't happen.
103+
// TL;DR: we'll be 1 comparison worse-off compared to the classic linear-
104+
// searching algorithm if matching position 3 of a set with 4 elements,
105+
// or position 5 if the set has 7 or 8 elements, but we'll never exceed
106+
// the complexity guarantees from the standard.
107+
_Iter __tmp = std::__lower_bound_onesided<_AlgPolicy>(__iter, __sentinel, __value, __comp_, __proj);
108+
std::swap(__tmp, __iter);
91109
__add_output_unless(__tmp != __iter);
92110
}
93111

@@ -137,7 +155,7 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
137155
std::forward_iterator_tag) {
138156
std::__set_intersector<_AlgPolicy, _Compare, _InForwardIter1, _Sent1, _InForwardIter2, _Sent2, _OutIter>
139157
__intersector(__first1, __last1, __first2, __last2, __result, __comp);
140-
return std::move(__intersector)();
158+
return __intersector();
141159
}
142160

143161
// input iterators are not suitable for multipass algorithms, so we stick to the classic single-pass version
@@ -183,7 +201,7 @@ class __set_intersection_iter_category {
183201
template <class _It>
184202
using __cat = typename std::_IterOps<_AlgPolicy>::template __iterator_category<_It>;
185203
template <class _It>
186-
static auto test(__cat<_It>*) -> __cat<_It>;
204+
static __cat<_It> test(__cat<_It>*);
187205
template <class>
188206
static std::input_iterator_tag test(...);
189207

@@ -202,7 +220,7 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
202220
std::move(__first2),
203221
std::move(__last2),
204222
std::move(__result),
205-
std::forward<_Compare>(__comp),
223+
__comp,
206224
typename std::__set_intersection_iter_category<_AlgPolicy, _InIter1>::__type(),
207225
typename std::__set_intersection_iter_category<_AlgPolicy, _InIter2>::__type());
208226
}

libcxx/test/std/algorithms/alg.sorting/alg.binary.search/lower.bound/lower_bound.pass.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,27 @@ template <class Iter, class T>
3939
void
4040
test(Iter first, Iter last, const T& value)
4141
{
42-
std::size_t strides{};
43-
std::size_t displacement{};
42+
#if TEST_STD_VER > 17
43+
std::size_t strides = 0;
44+
std::size_t displacement = 0;
4445
stride_counting_iterator f(first, &strides, &displacement);
4546
stride_counting_iterator l(last, &strides, &displacement);
47+
#else
48+
Iter& f = first;
49+
Iter& l = last;
50+
#endif
4651

4752
auto i = std::lower_bound(f, l, value);
4853
for (auto j = base(f); j != base(i); ++j)
4954
assert(*j < value);
5055
for (auto j = base(i); j != base(l); ++j)
5156
assert(!(*j < value));
5257

58+
#if TEST_STD_VER > 17
5359
auto len = static_cast<std::size_t>(std::distance(first, last));
5460
assert(strides <= 2 * len);
5561
assert(displacement <= 2 * len);
62+
#endif
5663
}
5764

5865
template <class Iter>

libcxx/test/std/algorithms/alg.sorting/alg.binary.search/lower.bound/lower_bound_comp.pass.cpp

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,27 @@ template <class Iter, class T>
3939
void
4040
test(Iter first, Iter last, const T& value)
4141
{
42-
std::size_t strides{};
43-
std::size_t displacement{};
42+
#if TEST_STD_VER > 17
43+
std::size_t strides = 0;
44+
std::size_t displacement = 0;
4445
stride_counting_iterator f(first, &strides, &displacement);
4546
stride_counting_iterator l(last, &strides, &displacement);
47+
#else
48+
Iter& f = first;
49+
Iter& l = last;
50+
#endif
51+
52+
std::size_t comparisons = 0;
53+
struct InstrumentedGreater {
54+
explicit InstrumentedGreater(std::size_t* cmp) : comparisons_(cmp) {}
55+
bool operator()(int rhs, int lhs) const {
56+
++*comparisons_;
57+
return std::greater<int>()(rhs, lhs);
58+
}
4659

47-
std::size_t comparisons{};
48-
auto cmp = [&comparisons](int rhs, int lhs) {
49-
++comparisons;
50-
return std::greater<int>()(rhs, lhs);
60+
std::size_t* comparisons_;
5161
};
62+
InstrumentedGreater cmp(&comparisons);
5263

5364
auto i = std::lower_bound(f, l, value, cmp);
5465
for (auto j = base(f); j != base(i); ++j)
@@ -57,8 +68,10 @@ test(Iter first, Iter last, const T& value)
5768
assert(!std::greater<int>()(*j, value));
5869

5970
auto len = static_cast<std::size_t>(std::distance(first, last));
71+
#if TEST_STD_VER > 17
6072
assert(strides <= 2 * len);
6173
assert(displacement <= 2 * len);
74+
#endif
6275
assert(comparisons <= std::ceil(std::log2(len + 1)));
6376
}
6477

libcxx/test/std/algorithms/alg.sorting/alg.set.operations/set.intersection/ranges_set_intersection.pass.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -436,20 +436,20 @@ constexpr void testComplexityParameterizedIter() {
436436
}
437437

438438
// Lower complexity when there is low overlap between ranges: we can make 2*log(X) comparisons when one range
439-
// has X elements that can be skipped over.
439+
// has X elements that can be skipped over (and then 1 more to confirm that the value we found is equal).
440440
{
441441
std::array r1{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
442442
std::array r2{15};
443443
std::array expected{15};
444444

445445
OperationCounts expectedCounts;
446-
expectedCounts.comparisons = 8;
447-
expectedCounts.in[0].proj = 8;
448-
expectedCounts.in[0].iterator_strides = 24;
449-
expectedCounts.in[0].iterator_displacement = 24;
450-
expectedCounts.in[1].proj = 8;
451-
expectedCounts.in[1].iterator_strides = 3;
452-
expectedCounts.in[1].iterator_displacement = 3;
446+
expectedCounts.comparisons = 9;
447+
expectedCounts.in[0].proj = 9;
448+
expectedCounts.in[0].iterator_strides = 23;
449+
expectedCounts.in[0].iterator_displacement = 23;
450+
expectedCounts.in[1].proj = 9;
451+
expectedCounts.in[1].iterator_strides = 1;
452+
expectedCounts.in[1].iterator_displacement = 1;
453453

454454
testSetIntersectionAndReturnOpCounts<In1, In2, Out>(r1, r2, expected, expectedCounts);
455455
testRangesSetIntersectionAndReturnOpCounts<In1, In2, Out>(r1, r2, expected, expectedCounts);
@@ -721,9 +721,9 @@ constexpr bool test() {
721721
std::ranges::set_intersection(r1.begin(), r1.end(), r2.begin(), r2.end(), out.data(), comp, proj1, proj2);
722722

723723
assert(std::ranges::equal(out, expected, {}, &Data::data));
724-
assert(numberOfComp < maxOperation);
725-
assert(numberOfProj1 < maxOperation);
726-
assert(numberOfProj2 < maxOperation);
724+
assert(numberOfComp <= maxOperation);
725+
assert(numberOfProj1 <= maxOperation);
726+
assert(numberOfProj2 <= maxOperation);
727727
}
728728

729729
// range overload

0 commit comments

Comments
 (0)