Implement the "binary search" ranges algorithm family (#917)

Includes `ranges::lower_bound`, `ranges::upper_bound`, `ranges::equal_range`, and `ranges::binary_search`.
This commit is contained in:
Casey Carter 2020-06-29 22:40:00 -07:00 коммит произвёл GitHub
Родитель c114d51fd1
Коммит ac5c8b006a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 411 добавлений и 0 удалений

Просмотреть файл

@ -4181,6 +4181,66 @@ _CONSTEXPR20 void sort_heap(_RanIt _First, _RanIt _Last) { // order heap by repe
_STD sort_heap(_First, _Last, less<>()); _STD sort_heap(_First, _Last, less<>());
} }
#ifdef __cpp_lib_concepts
namespace ranges {
// VARIABLE ranges::lower_bound
template <class _It, class _Ty, class _Pr, class _Pj>
_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 <forward_iterator _It, sentinel_for<_It> _Se, class _Ty, class _Pj = identity,
indirect_strict_weak_order<const _Ty*, projected<_It, _Pj>> _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 <forward_range _Rng, class _Ty, class _Pj = identity,
indirect_strict_weak_order<const _Ty*, projected<iterator_t<_Rng>, _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 // FUNCTION TEMPLATE upper_bound
template <class _FwdIt, class _Ty, class _Pr> template <class _FwdIt, class _Ty, class _Pr>
_NODISCARD _CONSTEXPR20 _FwdIt upper_bound(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) { _NODISCARD _CONSTEXPR20 _FwdIt upper_bound(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) {
@ -4210,6 +4270,64 @@ _NODISCARD _CONSTEXPR20 _FwdIt upper_bound(_FwdIt _First, _FwdIt _Last, const _T
return _STD upper_bound(_First, _Last, _Val, less<>()); return _STD upper_bound(_First, _Last, _Val, less<>());
} }
#ifdef __cpp_lib_concepts
namespace ranges {
// VARIABLE ranges::upper_bound
template <class _It, class _Ty, class _Pr, class _Pj>
_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 <forward_iterator _It, sentinel_for<_It> _Se, class _Ty, class _Pj = identity,
indirect_strict_weak_order<const _Ty*, projected<_It, _Pj>> _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 <forward_range _Rng, class _Ty, class _Pj = identity,
indirect_strict_weak_order<const _Ty*, projected<iterator_t<_Rng>, _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 // FUNCTION TEMPLATE equal_range
template <class _FwdIt, class _Ty, class _Pr> template <class _FwdIt, class _Ty, class _Pr>
_NODISCARD _CONSTEXPR20 pair<_FwdIt, _FwdIt> equal_range(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) { _NODISCARD _CONSTEXPR20 pair<_FwdIt, _FwdIt> equal_range(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) {
@ -4254,6 +4372,70 @@ _NODISCARD _CONSTEXPR20 pair<_FwdIt, _FwdIt> equal_range(_FwdIt _First, _FwdIt _
return _STD equal_range(_First, _Last, _Val, less<>()); 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 <forward_iterator _It, sentinel_for<_It> _Se, class _Ty, class _Pj = identity,
indirect_strict_weak_order<const _Ty*, projected<_It, _Pj>> _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<subrange<_It>>(_First, _STD move(_UResult));
}
template <forward_range _Rng, class _Ty, class _Pj = identity,
indirect_strict_weak_order<const _Ty*, projected<iterator_t<_Rng>, _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<borrowed_subrange_t<_Rng>>(_Result, _STD move(_UResult));
}
private:
template <class _It, class _Ty, class _Pr, class _Pj>
_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 // FUNCTION TEMPLATE binary_search
template <class _FwdIt, class _Ty, class _Pr> template <class _FwdIt, class _Ty, class _Pr>
_NODISCARD _CONSTEXPR20 bool binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) { _NODISCARD _CONSTEXPR20 bool binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred) {
@ -4271,6 +4453,40 @@ _NODISCARD _CONSTEXPR20 bool binary_search(_FwdIt _First, _FwdIt _Last, const _T
return _STD binary_search(_First, _Last, _Val, less<>()); 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 <forward_iterator _It, sentinel_for<_It> _Se, class _Ty, class _Pj = identity,
indirect_strict_weak_order<const _Ty*, projected<_It, _Pj>> _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 <forward_range _Rng, class _Ty, class _Pj = identity,
indirect_strict_weak_order<const _Ty*, projected<iterator_t<_Rng>, _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 // FUNCTION TEMPLATE merge
_NODISCARD constexpr _Distance_unknown _Idl_dist_add(_Distance_unknown, _Distance_unknown) { _NODISCARD constexpr _Distance_unknown _Idl_dist_add(_Distance_unknown, _Distance_unknown) {
return {}; return {};

Просмотреть файл

@ -238,6 +238,7 @@ tests\P0896R4_ranges_algorithm_machinery
tests\P0896R4_ranges_alg_adjacent_find tests\P0896R4_ranges_alg_adjacent_find
tests\P0896R4_ranges_alg_all_of tests\P0896R4_ranges_alg_all_of
tests\P0896R4_ranges_alg_any_of tests\P0896R4_ranges_alg_any_of
tests\P0896R4_ranges_alg_binary_search
tests\P0896R4_ranges_alg_copy tests\P0896R4_ranges_alg_copy
tests\P0896R4_ranges_alg_copy_if tests\P0896R4_ranges_alg_copy_if
tests\P0896R4_ranges_alg_copy_n tests\P0896R4_ranges_alg_copy_n

Просмотреть файл

@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
RUNALL_INCLUDE ..\concepts_matrix.lst

Просмотреть файл

@ -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 <algorithm>
#include <array>
#include <cassert>
#include <concepts>
#include <ranges>
#include <utility>
#include <range_algorithm_support.hpp>
using namespace std;
#define ASSERT(...) assert((__VA_ARGS__))
// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::lower_bound(borrowed<false>{}, 42)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::lower_bound(borrowed<true>{}, 42)), int*>);
STATIC_ASSERT(same_as<decltype(ranges::upper_bound(borrowed<false>{}, 42)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::upper_bound(borrowed<true>{}, 42)), int*>);
STATIC_ASSERT(same_as<decltype(ranges::equal_range(borrowed<false>{}, 42)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::equal_range(borrowed<true>{}, 42)), ranges::subrange<int*>>);
using P = pair<int, int>;
struct empty_ranges {
template <ranges::forward_range Fwd>
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<P, 10> 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 <ranges::forward_range Fwd>
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 <ranges::forward_range Fwd>
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 <ranges::forward_range Fwd>
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 <ranges::forward_range Fwd>
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<empty_ranges, const P>(), true));
test_fwd<empty_ranges, const P>();
STATIC_ASSERT((test_fwd<lower_bound_instantiator, const P>(), true));
test_fwd<lower_bound_instantiator, const P>();
STATIC_ASSERT((test_fwd<upper_bound_instantiator, const P>(), true));
test_fwd<upper_bound_instantiator, const P>();
STATIC_ASSERT((test_fwd<equal_range_instantiator, const P>(), true));
test_fwd<equal_range_instantiator, const P>();
STATIC_ASSERT((test_fwd<binary_search_instantiator, const P>(), true));
test_fwd<binary_search_instantiator, const P>();
}