Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<algorithm> Implement P2302R4 ranges::contains #2911

Merged
merged 8 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions stl/inc/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,78 @@ namespace ranges {

inline constexpr _None_of_fn none_of{_Not_quite_object::_Construct_tag{}};

#if _HAS_CXX23
class _Contains_fn : private _Not_quite_object {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
public:
using _Not_quite_object::_Not_quite_object;

// clang-format off
template <input_iterator _It, sentinel_for<_It> _Se, class _Ty, class _Pj = identity>
requires indirect_binary_predicate<ranges::equal_to, projected<_It, _Pj>, const _Ty*>
_NODISCARD constexpr bool operator()(_It _First, _Se _Last, const _Ty& _Val, _Pj _Proj = {}) const {
// clang-format on
_Adl_verify_range(_First, _Last);
const auto _ULast = _Get_unwrapped(_STD move(_Last));
const auto _UResult =
_RANGES _Find_unchecked(_Get_unwrapped(_STD move(_First)), _ULast, _Val, _Pass_fn(_Proj));
return _UResult != _ULast;
}

// clang-format off
template <input_range _Rng, class _Ty, class _Pj = identity>
requires indirect_binary_predicate<ranges::equal_to, projected<iterator_t<_Rng>, _Pj>, const _Ty*>
_NODISCARD constexpr bool operator()(_Rng&& _Range, const _Ty& _Val, _Pj _Proj = {}) const {
// clang-format on
const auto _UResult = _RANGES _Find_unchecked(_Ubegin(_Range), _Uend(_Range), _Val, _Pass_fn(_Proj));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we should memoize _Uend(_Range) as calling it twice might be expensive

Copy link
Contributor

@CaseyCarter CaseyCarter Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally don't avoid repeated calls to begin and end, because they are generally O(1). The first call may be expensive, but the subsequent calls are not. (Caching things across big function calls consumes stack and pushes us closer to cold stack space, so it's not a no-brainer.)

return _UResult != _Uend(_Range);
}
};

inline constexpr _Contains_fn contains{_Not_quite_object::_Construct_tag{}};

class _Contains_subrange_fn : private _Not_quite_object {
public:
using _Not_quite_object::_Not_quite_object;

// clang-format off
template <forward_iterator _It1, sentinel_for<_It1> _Se1, forward_iterator _It2, sentinel_for<_It2> _Se2,
class _Pr = ranges::equal_to, class _Pj1 = identity, class _Pj2 = identity>
requires indirectly_comparable<_It1, _It2, _Pr, _Pj1, _Pj2>
_NODISCARD constexpr bool operator()(_It1 _First1,
_Se1 _Last1, _It2 _First2, _Se2 _Last2, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const {
// clang-format on
_Adl_verify_range(_First2, _Last2);
auto _UFirst2 = _Get_unwrapped(_STD move(_First2));
auto _ULast2 = _Get_unwrapped(_STD move(_Last2));

if (_UFirst2 == _ULast2) {
return true;
}

const auto _Match = _RANGES search(_First1, _Last1, _STD move(_UFirst2), _STD move(_ULast2),
CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved
_Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2));
return !_Match.empty();
}

// clang-format off
template <forward_range _Rng1, forward_range _Rng2, class _Pr = ranges::equal_to, class _Pj1 = identity,
class _Pj2 = identity>
requires indirectly_comparable<iterator_t<_Rng1>, iterator_t<_Rng2>, _Pr, _Pj1, _Pj2>
_NODISCARD constexpr bool operator()(
_Rng1&& _Range1, _Rng2&& _Range2, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const {
// clang-format on
if (_RANGES empty(_Range2)) {
return true;
}

const auto _Match = _RANGES search(_Range1, _Range2, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2));
Copy link
Contributor

@miscco miscco Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we forward _Range1 and move _Range2

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lvalue ranges are strictly more capable than xvalue ranges, so forwarding is just noise for the purposes of algorithms.

return !_Match.empty();
}
};

inline constexpr _Contains_subrange_fn contains_subrange{_Not_quite_object::_Construct_tag{}};
#endif // _HAS_CXX23

template <class _In, class _Out>
using copy_n_result = in_out_result<_In, _Out>;

Expand Down
2 changes: 2 additions & 0 deletions stl/inc/yvals_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@
// P2166R1 Prohibiting basic_string And basic_string_view Construction From nullptr
// P2186R2 Removing Garbage Collection Support
// P2273R3 constexpr unique_ptr
// P2302R4 ranges::contains, ranges::contains_subrange
// P2321R2 zip
// (changes to pair, tuple, and vector<bool>::reference only)
// P2440R1 ranges::iota, ranges::shift_left, ranges::shift_right
Expand Down Expand Up @@ -1462,6 +1463,7 @@
#define __cpp_lib_out_ptr 202106L
#define __cpp_lib_ranges_chunk 202202L
#define __cpp_lib_ranges_chunk_by 202202L
#define __cpp_lib_ranges_contains 202207L
#define __cpp_lib_ranges_iota 202202L
#define __cpp_lib_ranges_join_with 202202L
#define __cpp_lib_ranges_slide 202202L
Expand Down
2 changes: 2 additions & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,8 @@ tests\P2136R3_invoke_r
tests\P2162R2_std_visit_for_derived_classes_from_variant
tests\P2231R1_complete_constexpr_optional_variant
tests\P2273R3_constexpr_unique_ptr
tests\P2302R4_ranges_alg_contains
tests\P2302R4_ranges_alg_contains_subrange
tests\P2321R2_proxy_reference
tests\P2401R0_conditional_noexcept_for_exchange
tests\P2415R2_owning_view
Expand Down
4 changes: 4 additions & 0 deletions tests/std/tests/P2302R4_ranges_alg_contains/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_latest_matrix.lst
65 changes: 65 additions & 0 deletions tests/std/tests/P2302R4_ranges_alg_contains/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <cassert>
#include <concepts>
#include <ranges>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;

struct instantiator {
static constexpr pair<int, int> haystack[3] = {{0, 42}, {2, 42}, {4, 42}};

template <ranges::input_range In>
static constexpr void call() {
using ranges::contains, ranges::begin, ranges::end;

for (const auto& [value, _] : haystack) {
{ // Validate range overload [found case]
const same_as<bool> auto result = contains(In{haystack}, value, get_first);
assert(result);
}
{ // Validate iterator + sentinel overload [found case]
const In wrapped{haystack};
const same_as<bool> auto result = contains(begin(wrapped), end(wrapped), value, get_first);
assert(result);
}
}
{
// Validate range overload [not found case]
const same_as<bool> auto result = contains(In{haystack}, 42, get_first);
assert(!result);
}
{
// Validate iterator + sentinel overload [not found case]
const In wrapped{haystack};
const same_as<bool> auto result = contains(begin(wrapped), end(wrapped), 42, get_first);
assert(!result);
}
{ // Validate memchr case
const char arr[5]{4, 8, 1, -15, 125};

// found case
same_as<bool> auto result = contains(arr, 1);
assert(result);

// not found case
result = contains(arr, 10);
assert(!result);
}
{ // unreachable_sentinel case
const In wrapped{haystack};
const same_as<bool> auto result = contains(begin(wrapped), unreachable_sentinel, 2, get_first);
assert(result);
}
}
};

int main() {
STATIC_ASSERT((test_in<instantiator, const pair<int, int>>(), true));
test_in<instantiator, const pair<int, int>>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_latest_matrix.lst
112 changes: 112 additions & 0 deletions tests/std/tests/P2302R4_ranges_alg_contains_subrange/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <cassert>
#include <concepts>
#include <span>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;

using Elem1 = const pair<int, int>;
using Elem2 = const int;

struct instantiator {
static constexpr pair<int, int> haystack[] = {{0, 42}, {1, 42}, {2, 42}, {3, 42}, {4, 42}, {5, 42}};
static constexpr int needle[] = {2, 3, 4};

template <ranges::forward_range Fwd1, ranges::forward_range Fwd2>
static constexpr void call() {
using ranges::contains_subrange, ranges::begin, ranges::end;

{ // Validate range overload [found case]
const same_as<bool> auto result =
contains_subrange(Fwd1{haystack}, Fwd2{needle}, ranges::equal_to{}, get_first);
assert(result);
}
{ // Validate iterator + sentinel overload [found case]
const Fwd1 wrap_hay{haystack};
const Fwd2 wrap_needle{needle};
const same_as<bool> auto result = contains_subrange(
begin(wrap_hay), end(wrap_hay), begin(wrap_needle), end(wrap_needle), ranges::equal_to{}, get_first);
assert(result);
}
{ // Validate range overload [not found case]
const same_as<bool> auto result =
contains_subrange(Fwd1{haystack}, Fwd2{needle}, ranges::equal_to{}, get_second);
assert(!result);
}
{ // Validate iterator + sentinel overload [not found case]
const Fwd1 wrap_hay{haystack};
const Fwd2 wrap_needle{needle};
const same_as<bool> auto result = contains_subrange(
begin(wrap_hay), end(wrap_hay), begin(wrap_needle), end(wrap_needle), ranges::equal_to{}, get_second);
assert(!result);
}
{ // Validate empty needle case
const span<Elem1> empty;
const same_as<bool> auto result = contains_subrange(Fwd1{haystack}, Fwd1{empty});
assert(result);
}
{ // Validate unreachable_sentinel case
const Fwd1 wrap_hay{haystack};
const Fwd2 wrap_needle{needle};
const same_as<bool> auto result = contains_subrange(begin(wrap_hay), unreachable_sentinel,
begin(wrap_needle), end(wrap_needle), ranges::equal_to{}, get_first);
assert(result);
}
}
};

#ifdef TEST_EVERYTHING
int main() {
STATIC_ASSERT((test_fwd_fwd<instantiator, Elem1, Elem2>(), true));
test_fwd_fwd<instantiator, Elem1, Elem2>();
}
#else // ^^^ test all range combinations // test only interesting range combos vvv
template <class Elem, test::Sized IsSized>
using fwd_test_range = test::range<forward_iterator_tag, Elem, IsSized, test::CanDifference::no, test::Common::no,
test::CanCompare::yes, test::ProxyRef::yes>;
template <class Elem, test::Sized IsSized, test::Common IsCommon>
using random_test_range = test::range<random_access_iterator_tag, Elem, IsSized, test::CanDifference::no, IsCommon,
test::CanCompare::yes, test::ProxyRef::no>;

constexpr bool run_tests() {
// All (except contiguous) proxy reference types, since the algorithm doesn't really care. Cases with only 1 range
// sized are not interesting; common is interesting only in that it's necessary to trigger memcmp optimization.

using test::Common, test::Sized;

// both forward, non-common, and sized or unsized
instantiator::call<fwd_test_range<Elem1, Sized::no>, fwd_test_range<Elem2, Sized::no>>();
instantiator::call<fwd_test_range<Elem1, Sized::yes>, fwd_test_range<Elem2, Sized::yes>>();

// both random-access, and sized or unsized; all permutations of common
instantiator::call<random_test_range<Elem1, Sized::no, Common::no>,
random_test_range<Elem2, Sized::no, Common::no>>();
instantiator::call<random_test_range<Elem1, Sized::no, Common::no>,
random_test_range<Elem2, Sized::no, Common::yes>>();
instantiator::call<random_test_range<Elem1, Sized::no, Common::yes>,
random_test_range<Elem2, Sized::no, Common::no>>();
instantiator::call<random_test_range<Elem1, Sized::no, Common::yes>,
random_test_range<Elem2, Sized::no, Common::yes>>();
instantiator::call<random_test_range<Elem1, Sized::yes, Common::no>,
random_test_range<Elem2, Sized::yes, Common::no>>();
instantiator::call<random_test_range<Elem1, Sized::yes, Common::no>,
random_test_range<Elem2, Sized::yes, Common::yes>>();
instantiator::call<random_test_range<Elem1, Sized::yes, Common::yes>,
random_test_range<Elem2, Sized::yes, Common::no>>();
instantiator::call<random_test_range<Elem1, Sized::yes, Common::yes>,
random_test_range<Elem2, Sized::yes, Common::yes>>();

return true;
}

int main() {
STATIC_ASSERT(run_tests());
run_tests();
}
#endif // TEST_EVERYTHING
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,20 @@ STATIC_ASSERT(__cpp_lib_ranges_chunk_by == 202202L);
#endif
#endif

#if _HAS_CXX23 && !defined(__EDG__) // TRANSITION, EDG concepts support
#ifndef __cpp_lib_ranges_contains
#error __cpp_lib_ranges_contains is not defined
#elif __cpp_lib_ranges_contains != 202207L
#error __cpp_lib_ranges_contains is not 202207L
#else
STATIC_ASSERT(__cpp_lib_ranges_contains == 202207L);
#endif
#else
#ifdef __cpp_lib_ranges_contains
#error __cpp_lib_ranges_contains is defined
#endif
#endif

#if _HAS_CXX23 && defined(__cpp_lib_concepts)
#ifndef __cpp_lib_ranges_iota
#error __cpp_lib_ranges_iota is not defined
Expand Down