Skip to content

Commit 4aa4a82

Browse files
committed
Address review comment about replacing struct __set_intersector with a function. I think I managed to preserve readability by keeping __add_output_unless() as a lambda.
1 parent 3c9f800 commit 4aa4a82

File tree

1 file changed

+44
-96
lines changed

1 file changed

+44
-96
lines changed

libcxx/include/__algorithm/set_intersection.h

Lines changed: 44 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -43,99 +43,18 @@ struct __set_intersection_result {
4343
: __in1_(std::move(__in_iter1)), __in2_(std::move(__in_iter2)), __out_(std::move(__out_iter)) {}
4444
};
4545

46-
template <class _AlgPolicy, class _Compare, class _InIter1, class _Sent1, class _InIter2, class _Sent2, class _OutIter>
47-
struct _LIBCPP_NODISCARD __set_intersector {
48-
_InIter1& __first1_;
49-
const _Sent1& __last1_;
50-
_InIter2& __first2_;
51-
const _Sent2& __last2_;
52-
_OutIter& __result_;
53-
_Compare& __comp_;
54-
bool __prev_advanced_ = true;
55-
56-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersector(
57-
_InIter1& __first1, _Sent1& __last1, _InIter2& __first2, _Sent2& __last2, _OutIter& __result, _Compare& __comp)
58-
: __first1_(__first1),
59-
__last1_(__last1),
60-
__first2_(__first2),
61-
__last2_(__last2),
62-
__result_(__result),
63-
__comp_(__comp) {}
64-
65-
_LIBCPP_NODISCARD _LIBCPP_HIDE_FROM_ABI
66-
_LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersection_result<_InIter1, _InIter2, _OutIter>
67-
operator()() {
68-
while (__first2_ != __last2_) {
69-
__advance1_and_maybe_add_result();
70-
if (__first1_ == __last1_)
71-
break;
72-
__advance2_and_maybe_add_result();
73-
}
74-
return __set_intersection_result<_InIter1, _InIter2, _OutIter>(
75-
_IterOps<_AlgPolicy>::next(std::move(__first1_), std::move(__last1_)),
76-
_IterOps<_AlgPolicy>::next(std::move(__first2_), std::move(__last2_)),
77-
std::move(__result_));
78-
}
79-
80-
private:
81-
// advance __iter to the first element in the range where !__comp_(__iter, __value)
82-
// add result if this is the second consecutive call without advancing
83-
// this method only works if you alternate calls between __advance1_and_maybe_add_result() and
84-
// __advance2_and_maybe_add_result()
85-
template <class _Iter, class _Sent, class _Value>
86-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void
87-
__advance_and_maybe_add_result(_Iter& __iter, const _Sent& __sentinel, const _Value& __value) {
88-
_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);
109-
__add_output_unless(__tmp != __iter);
110-
}
111-
112-
// advance __first1_ to the first element in the range where !__comp_(*__first1_, *__first2_)
113-
// add result if neither __first1_ nor __first2_ advanced in the last attempt (meaning they are equal)
114-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __advance1_and_maybe_add_result() {
115-
__advance_and_maybe_add_result(__first1_, __last1_, *__first2_);
116-
}
117-
118-
// advance __first2_ to the first element in the range where !__comp_(*__first2_, *__first1_)
119-
// add result if neither __first1_ nor __first2_ advanced in the last attempt (meaning they are equal)
120-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __advance2_and_maybe_add_result() {
121-
__advance_and_maybe_add_result(__first2_, __last2_, *__first1_);
122-
}
123-
124-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __add_output_unless(bool __advanced) {
125-
if (__advanced | __prev_advanced_) {
126-
__prev_advanced_ = __advanced;
127-
} else {
128-
*__result_ = *__first1_;
129-
++__result_;
130-
++__first1_;
131-
++__first2_;
132-
__prev_advanced_ = true;
133-
}
134-
}
135-
};
136-
137-
// with forward iterators we can make multiple passes over the data, allowing the use of one-sided binary search to reduce best-case
138-
// complexity to log(N)
46+
// With forward iterators we can make multiple passes over the data, allowing the use of one-sided binary search to
47+
// reduce best-case complexity to log(N). Understanding how we can use binary search and still respect complexity
48+
// guarantees is _not_ straightforward: the guarantee is "at most 2*(N+M)-1 comparisons", and one-sided binary search
49+
// will necessarily overshoot depending on the position of the needle in the haystack -- for instance, if we're
50+
// searching for 3 in (1, 2, 3, 4), we'll check if 3<1, then 3<2, then 3<4, and, finally, 3<3, for a total of 4
51+
// comparisons, when linear search would have yielded 3. However, because we won't need to perform the intervening
52+
// reciprocal comparisons (ie 1<3, 2<3, 4<3), that extra comparison doesn't run afoul of the guarantee. Additionally,
53+
// this type of scenario can only happen for match distances of up to 5 elements, because 2*log2(8) is 6, and we'll
54+
// still be worse-off at position 5 of an 8-element set. From then onwards these scenarios can't happen. TL;DR: we'll be
55+
// 1 comparison worse-off compared to the classic linear- searching algorithm if matching position 3 of a set with 4
56+
// elements, or position 5 if the set has 7 or 8 elements, but we'll never exceed the complexity guarantees from the
57+
// standard.
13958
template <class _AlgPolicy,
14059
class _Compare,
14160
class _InForwardIter1,
@@ -154,9 +73,38 @@ __set_intersection(
15473
_Compare&& __comp,
15574
std::forward_iterator_tag,
15675
std::forward_iterator_tag) {
157-
std::__set_intersector<_AlgPolicy, _Compare, _InForwardIter1, _Sent1, _InForwardIter2, _Sent2, _OutIter>
158-
__intersector(__first1, __last1, __first2, __last2, __result, __comp);
159-
return __intersector();
76+
_LIBCPP_CONSTEXPR std::__identity __proj;
77+
bool __prev_advanced = true;
78+
79+
auto __add_output_unless = [&](bool __advanced) {
80+
if (__advanced | __prev_advanced) {
81+
__prev_advanced = __advanced;
82+
} else {
83+
*__result = *__first1;
84+
++__result;
85+
++__first1;
86+
++__first2;
87+
__prev_advanced = true;
88+
}
89+
};
90+
91+
while (__first2 != __last2) {
92+
_InForwardIter1 __first1_next =
93+
std::__lower_bound_onesided<_AlgPolicy>(__first1, __last1, *__first2, __comp, __proj);
94+
std::swap(__first1_next, __first1);
95+
__add_output_unless(__first1 != __first1_next);
96+
if (__first1 == __last1)
97+
break;
98+
99+
_InForwardIter2 __first2_next =
100+
std::__lower_bound_onesided<_AlgPolicy>(__first2, __last2, *__first1, __comp, __proj);
101+
std::swap(__first2_next, __first2);
102+
__add_output_unless(__first2 != __first2_next);
103+
}
104+
return __set_intersection_result<_InForwardIter1, _InForwardIter2, _OutIter>(
105+
_IterOps<_AlgPolicy>::next(std::move(__first1), std::move(__last1)),
106+
_IterOps<_AlgPolicy>::next(std::move(__first2), std::move(__last2)),
107+
std::move(__result));
160108
}
161109

162110
// input iterators are not suitable for multipass algorithms, so we stick to the classic single-pass version

0 commit comments

Comments
 (0)