diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 049e11eb817..691f44e08e4 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -4018,6 +4018,66 @@ _CONSTEXPR20 void sort_heap(_RanIt _First, _RanIt _Last) { // order heap by repe _STD sort_heap(_First, _Last, less<>()); } +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::lower_bound + template + _NODISCARD constexpr _It _Lower_bound_unchecked( + _It _First, iter_difference_t<_It> _Count, const _Ty& _Val, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_strict_weak_order<_Pr, const _Ty*, projected<_It, _Pj>>); + + using _Diff = iter_difference_t<_It>; + + while (_Count > 0) { // divide and conquer, check midpoint + const auto _Half = static_cast<_Diff>(_Count / 2); + auto _Mid = _RANGES next(_First, _Half); + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Mid), _Val)) { // try top half + _First = _STD move(_Mid); + ++_First; + _Count -= static_cast<_Diff>(_Half + 1); + } else { // try bottom half + _Count = _Half; + } + } + + return _First; + } + + class _Lower_bound_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Ty, class _Pj = identity, + indirect_strict_weak_order> _Pr = ranges::less> + _NODISCARD constexpr _It operator()( + _It _First, _Se _Last, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UFirst = _Get_unwrapped(_STD move(_First)); + const auto _Count = _RANGES distance(_UFirst, _Get_unwrapped(_STD move(_Last))); + _UFirst = + _RANGES _Lower_bound_unchecked(_STD move(_UFirst), _Count, _Val, _Pass_fn(_Pred), _Pass_fn(_Proj)); + _Seek_wrapped(_First, _STD move(_UFirst)); + return _First; + } + + template , _Pj>> _Pr = ranges::less> + _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()( + _Rng&& _Range, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + const auto _Count = _RANGES distance(_Range); + auto _UResult = + _RANGES _Lower_bound_unchecked(_Ubegin(_Range), _Count, _Val, _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _Result = _RANGES begin(_Range); + _Seek_wrapped(_Result, _STD move(_UResult)); + return _Result; + } + }; + + inline constexpr _Lower_bound_fn lower_bound{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE upper_bound template _NODISCARD _CONSTEXPR20 _FwdIt upper_bound(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) { @@ -4047,6 +4107,64 @@ _NODISCARD _CONSTEXPR20 _FwdIt upper_bound(_FwdIt _First, _FwdIt _Last, const _T return _STD upper_bound(_First, _Last, _Val, less<>()); } +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::upper_bound + template + _NODISCARD constexpr _It _Upper_bound_unchecked( + _It _First, iter_difference_t<_It> _Count, const _Ty& _Val, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_strict_weak_order<_Pr, const _Ty*, projected<_It, _Pj>>); + + using _Diff = iter_difference_t<_It>; + + while (_Count > 0) { // divide and conquer: find half that contains answer + const auto _Half = static_cast<_Diff>(_Count / 2); + auto _Mid = _RANGES next(_First, _Half); + if (_STD invoke(_Pred, _Val, _STD invoke(_Proj, *_Mid))) { + _Count = _Half; + } else { // try top half + _First = _STD move(_Mid); + ++_First; + _Count -= static_cast<_Diff>(_Half + 1); + } + } + + return _First; + } + + class _Upper_bound_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Ty, class _Pj = identity, + indirect_strict_weak_order> _Pr = ranges::less> + _NODISCARD constexpr _It operator()( + _It _First, _Se _Last, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UFirst = _Get_unwrapped(_STD move(_First)); + const auto _Count = _RANGES distance(_UFirst, _Get_unwrapped(_STD move(_Last))); + _UFirst = _Upper_bound_unchecked(_STD move(_UFirst), _Count, _Val, _Pass_fn(_Pred), _Pass_fn(_Proj)); + _Seek_wrapped(_First, _STD move(_UFirst)); + return _First; + } + + template , _Pj>> _Pr = ranges::less> + _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()( + _Rng&& _Range, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + const auto _Count = _RANGES distance(_Range); + auto _UResult = _Upper_bound_unchecked(_Ubegin(_Range), _Count, _Val, _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _Result = _RANGES begin(_Range); + _Seek_wrapped(_Result, _STD move(_UResult)); + return _Result; + } + }; + + inline constexpr _Upper_bound_fn upper_bound{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE equal_range template _NODISCARD _CONSTEXPR20 pair<_FwdIt, _FwdIt> equal_range(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) { @@ -4091,6 +4209,70 @@ _NODISCARD _CONSTEXPR20 pair<_FwdIt, _FwdIt> equal_range(_FwdIt _First, _FwdIt _ return _STD equal_range(_First, _Last, _Val, less<>()); } +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::equal_range + class _Equal_range_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Ty, class _Pj = identity, + indirect_strict_weak_order> _Pr = ranges::less> + _NODISCARD constexpr subrange<_It> operator()( + _It _First, _Se _Last, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UFirst = _Get_unwrapped(_STD move(_First)); + const auto _Count = _RANGES distance(_UFirst, _Get_unwrapped(_STD move(_Last))); + auto _UResult = _Equal_range_unchecked(_STD move(_UFirst), _Count, _Val, _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _Rewrap_subrange>(_First, _STD move(_UResult)); + } + + template , _Pj>> _Pr = ranges::less> + _NODISCARD constexpr borrowed_subrange_t<_Rng> operator()( + _Rng&& _Range, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + const auto _Count = _RANGES distance(_Range); + auto _UResult = _Equal_range_unchecked(_Ubegin(_Range), _Count, _Val, _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _Result = _RANGES begin(_Range); + return _Rewrap_subrange>(_Result, _STD move(_UResult)); + } + + private: + template + _NODISCARD static constexpr subrange<_It> _Equal_range_unchecked( + _It _First, iter_difference_t<_It> _Count, const _Ty& _Val, _Pr _Pred, _Pj _Proj) { + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(indirect_strict_weak_order<_Pr, const _Ty*, projected<_It, _Pj>>); + + using _Diff = iter_difference_t<_It>; + + while (_Count > 0) { // divide and conquer, check midpoint + const auto _Half = static_cast<_Diff>(_Count / 2); + auto _Mid = _RANGES next(_First, _Half); + + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Mid), _Val)) { // range in second half + _First = _STD move(_Mid); + ++_First; + _Count -= static_cast<_Diff>(_Half + 1); + } else if (_STD invoke(_Pred, _Val, _STD invoke(_Proj, *_Mid))) { + _Count = _Half; // range in first half + } else { // range straddles _Mid, find the ends + _First = _RANGES _Lower_bound_unchecked(_STD move(_First), _Half, _Val, _Pred, _Proj); + ++_Mid; + _Count -= static_cast<_Diff>(_Half + 1); + _Mid = _RANGES _Upper_bound_unchecked(_STD move(_Mid), _Count, _Val, _Pred, _Proj); + return {_STD move(_First), _STD move(_Mid)}; + } + } + + return {_First, _First}; + } + }; + + inline constexpr _Equal_range_fn equal_range{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE binary_search template _NODISCARD _CONSTEXPR20 bool binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) { @@ -4108,6 +4290,40 @@ _NODISCARD _CONSTEXPR20 bool binary_search(_FwdIt _First, _FwdIt _Last, const _T return _STD binary_search(_First, _Last, _Val, less<>()); } +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::binary_search + class _Binary_search_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Ty, class _Pj = identity, + indirect_strict_weak_order> _Pr = ranges::less> + _NODISCARD constexpr bool operator()( + _It _First, _Se _Last, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UFirst = _Get_unwrapped(_STD move(_First)); + const auto _ULast = _Get_unwrapped(_STD move(_Last)); + const auto _Count = _RANGES distance(_UFirst, _ULast); + _UFirst = + _RANGES _Lower_bound_unchecked(_STD move(_UFirst), _Count, _Val, _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _UFirst != _ULast && !_STD invoke(_Pred, _Val, _STD invoke(_Proj, *_UFirst)); + } + + template , _Pj>> _Pr = ranges::less> + _NODISCARD constexpr bool operator()(_Rng&& _Range, const _Ty& _Val, _Pr _Pred = {}, _Pj _Proj = {}) const { + const auto _Count = _RANGES distance(_Range); + const auto _UFirst = + _RANGES _Lower_bound_unchecked(_Ubegin(_Range), _Count, _Val, _Pass_fn(_Pred), _Pass_fn(_Proj)); + return _UFirst != _Uend(_Range) && !_STD invoke(_Pred, _Val, _STD invoke(_Proj, *_UFirst)); + } + }; + + inline constexpr _Binary_search_fn binary_search{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE merge _NODISCARD constexpr _Distance_unknown _Idl_dist_add(_Distance_unknown, _Distance_unknown) { return {}; diff --git a/tests/std/test.lst b/tests/std/test.lst index 8672000b159..b2fc023eb76 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -237,6 +237,7 @@ tests\P0896R4_ranges_algorithm_machinery tests\P0896R4_ranges_alg_adjacent_find tests\P0896R4_ranges_alg_all_of tests\P0896R4_ranges_alg_any_of +tests\P0896R4_ranges_alg_binary_search tests\P0896R4_ranges_alg_copy tests\P0896R4_ranges_alg_copy_if tests\P0896R4_ranges_alg_copy_n diff --git a/tests/std/tests/P0896R4_ranges_alg_binary_search/env.lst b/tests/std/tests/P0896R4_ranges_alg_binary_search/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_binary_search/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0896R4_ranges_alg_binary_search/test.cpp b/tests/std/tests/P0896R4_ranges_alg_binary_search/test.cpp new file mode 100644 index 00000000000..0d25a818b98 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_binary_search/test.cpp @@ -0,0 +1,190 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Covers ranges::lower_bound, ranges::upper_bound, ranges::equal_range, and ranges::binary_search + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +#define ASSERT(...) assert((__VA_ARGS__)) + +// Validate dangling story +STATIC_ASSERT(same_as{}, 42)), ranges::dangling>); +STATIC_ASSERT(same_as{}, 42)), int*>); +STATIC_ASSERT(same_as{}, 42)), ranges::dangling>); +STATIC_ASSERT(same_as{}, 42)), int*>); +STATIC_ASSERT(same_as{}, 42)), ranges::dangling>); +STATIC_ASSERT(same_as{}, 42)), ranges::subrange>); + +using P = pair; + +struct empty_ranges { + template + static constexpr void call() { + // Validate empty ranges + const Fwd range{}; + + ASSERT(ranges::lower_bound(range, 42, ranges::less{}, get_first) == ranges::end(range)); + ASSERT(ranges::lower_bound(ranges::begin(range), ranges::end(range), 42, ranges::less{}, get_first) + == ranges::end(range)); + + ASSERT(ranges::upper_bound(range, 42, ranges::less{}, get_first) == ranges::end(range)); + ASSERT(ranges::upper_bound(ranges::begin(range), ranges::end(range), 42, ranges::less{}, get_first) + == ranges::end(range)); + + { + auto result = ranges::equal_range(range, 42, ranges::less{}, get_first); + ASSERT(result.begin() == ranges::end(range)); + ASSERT(result.end() == ranges::end(range)); + } + { + auto result = ranges::equal_range(ranges::begin(range), ranges::end(range), 42, ranges::less{}, get_first); + ASSERT(result.begin() == ranges::end(range)); + ASSERT(result.end() == ranges::end(range)); + } + + ASSERT(!ranges::binary_search(range, 42, ranges::less{}, get_first)); + ASSERT(!ranges::binary_search(ranges::begin(range), ranges::end(range), 42, ranges::less{}, get_first)); + } +}; + +static constexpr array pairs = { + P{0, 100}, P{1, 90}, P{2, 80}, P{3, 70}, P{3, 60}, P{3, 50}, P{4, 40}, P{5, 30}, P{6, 20}, P{7, 10}}; + +struct lower_bound_instantiator { + template + static constexpr void call() { + const Fwd range{pairs}; + + ASSERT(ranges::lower_bound(range, -1, ranges::less{}, get_first) == ranges::begin(range)); + ASSERT(ranges::lower_bound(ranges::begin(range), ranges::end(range), -1, ranges::less{}, get_first) + == ranges::begin(range)); + + for (int i = 0; i < 8; ++i) { + const P* const target = pairs.data() + i + (i > 3 ? 2 : 0); + ASSERT(to_address(ranges::lower_bound(range, i, ranges::less{}, get_first).base()) == target); + ASSERT( + to_address( + ranges::lower_bound(ranges::begin(range), ranges::end(range), i, ranges::less{}, get_first).base()) + == target); + } + + ASSERT(ranges::lower_bound(range, 8, ranges::less{}, get_first) == ranges::end(range)); + ASSERT(ranges::lower_bound(ranges::begin(range), ranges::end(range), 8, ranges::less{}, get_first) + == ranges::end(range)); + } +}; + +struct upper_bound_instantiator { + template + static constexpr void call() { + const Fwd range{pairs}; + + ASSERT(ranges::upper_bound(range, -1, ranges::less{}, get_first) == ranges::begin(range)); + ASSERT(ranges::upper_bound(ranges::begin(range), ranges::end(range), -1, ranges::less{}, get_first) + == ranges::begin(range)); + + for (int i = 0; i < 8; ++i) { + const P* const target = pairs.data() + i + 1 + (i >= 3 ? 2 : 0); + + ASSERT(to_address(ranges::upper_bound(range, i, ranges::less{}, get_first).base()) == target); + ASSERT( + to_address( + ranges::upper_bound(ranges::begin(range), ranges::end(range), i, ranges::less{}, get_first).base()) + == target); + } + + ASSERT(ranges::upper_bound(range, 8, ranges::less{}, get_first) == ranges::end(range)); + ASSERT(ranges::upper_bound(ranges::begin(range), ranges::end(range), 8, ranges::less{}, get_first) + == ranges::end(range)); + } +}; + +struct equal_range_instantiator { + template + static constexpr void call() { + const Fwd range{pairs}; + + { + auto result = ranges::equal_range(range, -1, ranges::less{}, get_first); + ASSERT(result.begin() == ranges::begin(range)); + ASSERT(result.end() == ranges::begin(range)); + } + { + auto result = ranges::equal_range(ranges::begin(range), ranges::end(range), -1, ranges::less{}, get_first); + ASSERT(result.begin() == ranges::begin(range)); + ASSERT(result.end() == ranges::begin(range)); + } + + for (int i = 0; i < 8; ++i) { + const P* const lo = pairs.data() + i + (i > 3 ? 2 : 0); + const P* const hi = pairs.data() + i + 1 + (i >= 3 ? 2 : 0); + + { + auto result = ranges::equal_range(range, i, ranges::less{}, get_first); + ASSERT(to_address(result.begin().base()) == lo); + ASSERT(to_address(result.end().base()) == hi); + } + { + auto result = + ranges::equal_range(ranges::begin(range), ranges::end(range), i, ranges::less{}, get_first); + ASSERT(to_address(result.begin().base()) == lo); + ASSERT(to_address(result.end().base()) == hi); + } + } + + { + auto result = ranges::equal_range(range, 8, ranges::less{}, get_first); + ASSERT(result.begin() == ranges::end(range)); + ASSERT(result.end() == ranges::end(range)); + } + { + auto result = ranges::equal_range(ranges::begin(range), ranges::end(range), 8, ranges::less{}, get_first); + ASSERT(result.begin() == ranges::end(range)); + ASSERT(result.end() == ranges::end(range)); + } + } +}; + +struct binary_search_instantiator { + template + static constexpr void call() { + const Fwd range{pairs}; + + ASSERT(!ranges::binary_search(range, -1, ranges::less{}, get_first)); + ASSERT(!ranges::binary_search(ranges::begin(range), ranges::end(range), -1, ranges::less{}, get_first)); + + for (int i = 0; i < 8; ++i) { + ASSERT(ranges::binary_search(range, i, ranges::less{}, get_first)); + ASSERT(ranges::binary_search(ranges::begin(range), ranges::end(range), i, ranges::less{}, get_first)); + } + + ASSERT(!ranges::binary_search(range, 8, ranges::less{}, get_first)); + ASSERT(!ranges::binary_search(ranges::begin(range), ranges::end(range), 8, ranges::less{}, get_first)); + } +}; + +int main() { + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); + + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); + + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); + + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); + + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); +}