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

P1223R5: find_last, et al. #3268

Merged
merged 11 commits into from
Dec 15, 2022
214 changes: 214 additions & 0 deletions stl/inc/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,220 @@ namespace ranges {
};

_EXPORT_STD inline constexpr _Fold_right_last_fn fold_right_last{_Not_quite_object::_Construct_tag{}};

class _Find_last_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>
requires indirect_binary_predicate<ranges::equal_to, projected<_It, _Pj>, const _Ty*>
_NODISCARD constexpr subrange<_It> operator()(_It _First, _Se _Sent, const _Ty& _Value, _Pj _Proj = {}) const {
_Adl_verify_range(_First, _Sent);

auto [_UFirst, _ULast] = _Find_last_unchecked(
_Unwrap_iter<_Se>(_STD move(_First)), _Unwrap_sent<_It>(_STD move(_Sent)), _Value, _Pass_fn(_Proj));
_It _Last{};
_Seek_wrapped(_First, _STD move(_UFirst));
_Seek_wrapped(_Last, _STD move(_ULast));
return {_STD move(_First), _STD move(_Last)};
SuperWig marked this conversation as resolved.
Show resolved Hide resolved
}

template <forward_range _Rng, class _Ty, class _Pj = identity>
requires indirect_binary_predicate<ranges::equal_to, projected<iterator_t<_Rng>, _Pj>, const _Ty*>
_NODISCARD constexpr borrowed_subrange_t<_Rng> operator()(
_Rng&& _Range, const _Ty& _Value, _Pj _Proj = {}) const {
auto _First = _RANGES begin(_Range);
auto [_UFirst, _ULast] = _Find_last_unchecked(
_Unwrap_range_iter<_Rng>(_STD move(_First)), _Uend(_Range), _Value, _Pass_fn(_Proj));
iterator_t<_Rng> _Last{};
_Seek_wrapped(_First, _STD move(_UFirst));
_Seek_wrapped(_Last, _STD move(_ULast));
return {_STD move(_First), _STD move(_Last)};
}

private:
template <class _It, class _Se, class _Ty, class _Pj>
_NODISCARD static constexpr subrange<_It> _Find_last_unchecked(
_It _First, _Se _Last, const _Ty& _Value, _Pj _Proj) {
_STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>);
_STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>);
_STL_INTERNAL_STATIC_ASSERT(indirect_binary_predicate<ranges::equal_to, projected<_It, _Pj>, const _Ty*>);

if constexpr (_Bidi_common<_It, _Se>) {
for (auto _Result = _Last; _Result != _First;) {
if (_STD invoke(_Proj, *--_Result) == _Value) {
return {_STD move(_Result), _STD move(_Last)};
}
}
return {_Last, _Last};
} else if constexpr (same_as<_It, _Se>) {
auto _Result = _Last;
for (; _First != _Last; ++_First) {
if (_STD invoke(_Proj, *_First) == _Value) {
_Result = _First;
}
}
return {_STD move(_Result), _STD move(_Last)};
} else {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
_It _Result{};
for (;;) {
if (_STD invoke(_Proj, *_First) == _Value) {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
_Result = _First;
}
if (++_First == _Last) {
SuperWig marked this conversation as resolved.
Show resolved Hide resolved
if (_Result == _It{}) {
_Result = _First;
}
break;
}
}
return {_STD move(_Result), _STD move(_First)};
}
}
};

_EXPORT_STD inline constexpr _Find_last_fn find_last{_Not_quite_object::_Construct_tag{}};

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

template <forward_iterator _It, sentinel_for<_It> _Se, class _Pj = identity,
indirect_unary_predicate<projected<_It, _Pj>> _Pr>
_NODISCARD constexpr subrange<_It> operator()(_It _First, _Se _Sent, _Pr _Pred, _Pj _Proj = {}) const {
_Adl_verify_range(_First, _Sent);

auto [_UFirst, _ULast] = _Find_last_if_unchecked(_Unwrap_iter<_Se>(_STD move(_First)),
_Unwrap_sent<_It>(_STD move(_Sent)), _Pass_fn(_Pred), _Pass_fn(_Proj));
_It _Last{};
_Seek_wrapped(_First, _STD move(_UFirst));
_Seek_wrapped(_Last, _STD move(_ULast));
return {_STD move(_First), _STD move(_Last)};
}
template <forward_range _Rng, class _Pj = identity,
SuperWig marked this conversation as resolved.
Show resolved Hide resolved
indirect_unary_predicate<projected<iterator_t<_Rng>, _Pj>> _Pr>
constexpr borrowed_subrange_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const {
SuperWig marked this conversation as resolved.
Show resolved Hide resolved
auto _First = _RANGES begin(_Range);
auto [_UFirst, _ULast] = _Find_last_if_unchecked(
_Unwrap_range_iter<_Rng>(_STD move(_First)), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj));
iterator_t<_Rng> _Last{};
_Seek_wrapped(_First, _STD move(_UFirst));
_Seek_wrapped(_Last, _STD move(_ULast));
return {_STD move(_First), _STD move(_Last)};
}

private:
template <class _It, class _Se, class _Pj, class _Pr>
_NODISCARD static constexpr subrange<_It> _Find_last_if_unchecked(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj) {
_STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>);
_STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>);
_STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>);

if constexpr (_Bidi_common<_It, _Se>) {
for (auto _Result = _Last; _Result != _First;) {
if (_STD invoke(_Pred, _STD invoke(_Proj, *--_Result))) {
return {_STD move(_Result), _STD move(_Last)};
}
}
return {_Last, _Last};
} else if constexpr (same_as<_It, _Se>) {
auto _Result = _Last;
for (; _First != _Last; ++_First) {
if (_STD invoke(_Pred, _STD invoke(_Proj, *_First))) {
_Result = _First;
}
}
return {_STD move(_Result), _STD move(_Last)};
} else {
_It _Result{};
for (;;) {
if (_STD invoke(_Pred, _STD invoke(_Proj, *_First))) {
_Result = _First;
}
if (++_First == _Last) {
if (_Result == _It{}) {
_Result = _First;
}
break;
}
}
return {_STD move(_Result), _STD move(_First)};
}
}
};

_EXPORT_STD inline constexpr _Find_last_if_fn find_last_if{_Not_quite_object::_Construct_tag{}};

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

template <forward_iterator _It, sentinel_for<_It> _Se, class _Pj = identity,
indirect_unary_predicate<projected<_It, _Pj>> _Pr>
_NODISCARD constexpr subrange<_It> operator()(_It _First, _Se _Sent, _Pr _Pred, _Pj _Proj = {}) const {
_Adl_verify_range(_First, _Sent);

auto [_UFirst, _ULast] = _Find_last_if_not_unchecked(_Unwrap_iter<_Se>(_STD move(_First)),
_Unwrap_sent<_It>(_STD move(_Sent)), _Pass_fn(_Pred), _Pass_fn(_Proj));
_It _Last{};
_Seek_wrapped(_First, _STD move(_UFirst));
_Seek_wrapped(_Last, _STD move(_ULast));
return {_STD move(_First), _STD move(_Last)};
}
template <forward_range _Rng, class _Pj = identity,
CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved
indirect_unary_predicate<projected<iterator_t<_Rng>, _Pj>> _Pr>
constexpr borrowed_subrange_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const {
SuperWig marked this conversation as resolved.
Show resolved Hide resolved
auto _First = _RANGES begin(_Range);
auto [_UFirst, _ULast] = _Find_last_if_not_unchecked(
_Unwrap_range_iter<_Rng>(_STD move(_First)), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj));
iterator_t<_Rng> _Last{};
_Seek_wrapped(_First, _STD move(_UFirst));
_Seek_wrapped(_Last, _STD move(_ULast));
return {_STD move(_First), _STD move(_Last)};
}

private:
template <class _It, class _Se, class _Pj, class _Pr>
_NODISCARD static constexpr subrange<_It> _Find_last_if_not_unchecked(
_It _First, _Se _Last, _Pr _Pred, _Pj _Proj) {
_STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It>);
_STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>);
_STL_INTERNAL_STATIC_ASSERT(indirect_unary_predicate<_Pr, projected<_It, _Pj>>);

if constexpr (_Bidi_common<_It, _Se>) {
for (auto _Result = _Last; _Result != _First;) {
if (!_STD invoke(_Pred, _STD invoke(_Proj, *--_Result))) {
return {_STD move(_Result), _STD move(_Last)};
}
}
return {_Last, _Last};
} else if constexpr (same_as<_It, _Se>) {
auto _Result = _Last;
for (; _First != _Last; ++_First) {
if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) {
_Result = _First;
}
}
return {_STD move(_Result), _STD move(_Last)};
} else {
_It _Result{};
for (;;) {
if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First))) {
_Result = _First;
}
if (++_First == _Last) {
if (_Result == _It{}) {
_Result = _First;
}
break;
}
}
return {_STD move(_Result), _STD move(_First)};
}
}
};

_EXPORT_STD inline constexpr _Find_last_if_not_fn find_last_if_not{_Not_quite_object::_Construct_tag{}};
CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved
#endif // _HAS_CXX23
} // namespace ranges
#endif // __cpp_lib_concepts
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 @@ -307,6 +307,7 @@
// P1132R7 out_ptr(), inout_ptr()
// P1147R1 Printing volatile Pointers
// P1206R7 Conversions From Ranges To Containers
// P1223R5 ranges::find_last, ranges::find_last_if, ranges::find_last_if_not
// P1272R4 byteswap()
// P1328R1 constexpr type_info::operator==()
// P1413R3 Deprecate aligned_storage And aligned_union
Expand Down Expand Up @@ -1677,6 +1678,7 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect
#define __cpp_lib_ranges_chunk 202202L
#define __cpp_lib_ranges_chunk_by 202202L
#define __cpp_lib_ranges_contains 202207L
#define __cpp_lib_ranges_find_last 202207L // per LWG-3807
#define __cpp_lib_ranges_fold 202207L
#define __cpp_lib_ranges_iota 202202L
#define __cpp_lib_ranges_join_with 202202L
Expand Down
3 changes: 3 additions & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ tests\P1206R7_vector_assign_range
tests\P1206R7_vector_from_range
tests\P1206R7_vector_insert_range
tests\P1208R6_source_location
tests\P1223R5_find_last
tests\P1223R5_find_last_if
tests\P1223R5_find_last_if_not
SuperWig marked this conversation as resolved.
Show resolved Hide resolved
tests\P1272R4_byteswap
tests\P1423R3_char8_t_remediation
tests\P1425R4_queue_stack_constructors
Expand Down
4 changes: 4 additions & 0 deletions tests/std/tests/P1223R5_ranges_alg_find_last/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
70 changes: 70 additions & 0 deletions tests/std/tests/P1223R5_ranges_alg_find_last/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <cassert>
#include <concepts>
SuperWig marked this conversation as resolved.
Show resolved Hide resolved
#include <ranges>
#include <utility>

#include <range_algorithm_support.hpp>
using namespace std;
using P = pair<int, int>;

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::find_last(borrowed<false>{}, 42)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::find_last(borrowed<true>{}, 42)), ranges::subrange<int*>>);

template <class T, class U>
constexpr void check_value(const T& found, const U& value) {
if constexpr (same_as<T, P>) {
assert(found.first == value);
} else {
assert(found.peek().first == value);
}
}

struct instantiator {
static constexpr P haystack[3] = {{0, 42}, {2, 42}, {4, 42}};
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

template <ranges::forward_range Read>
static constexpr void call() {
using ranges::find_last, ranges::common_range, ranges::iterator_t;

for (const auto& [value, _] : haystack) {
{ // Validate range overload [found case]
Read wrapped_input{haystack};
const auto result = find_last(wrapped_input, value, get_first);
STATIC_ASSERT(same_as<iterator_t<decltype(result)>, iterator_t<Read>>);
STATIC_ASSERT(common_range<decltype(result)>);
SuperWig marked this conversation as resolved.
Show resolved Hide resolved
check_value(result.front(), value);
}
{ // Validate iterator + sentinel overload [found case]
Read wrapped_input{haystack};
auto result = find_last(wrapped_input.begin(), wrapped_input.end(), value, get_first);
STATIC_ASSERT(same_as<iterator_t<decltype(result)>, iterator_t<Read>>);
STATIC_ASSERT(common_range<decltype(result)>);
check_value(result.front(), value);
}
}
{ // Validate range overload [not found case]
Read wrapped_input{haystack};
auto result = find_last(wrapped_input, 42, get_first);
STATIC_ASSERT(same_as<iterator_t<decltype(result)>, iterator_t<Read>>);
STATIC_ASSERT(common_range<decltype(result)>);
assert(result.begin() == wrapped_input.end());
}
{ // Validate iterator + sentinel overload [not found case]
Read wrapped_input{haystack};
auto result = find_last(wrapped_input.begin(), wrapped_input.end(), 42, get_first);
STATIC_ASSERT(same_as<iterator_t<decltype(result)>, iterator_t<Read>>);
STATIC_ASSERT(common_range<decltype(result)>);
assert(result.begin() == wrapped_input.end());
}
}
};

int main() {
STATIC_ASSERT((test_fwd<instantiator, const P>(), true));
test_fwd<instantiator, const P>();
}
4 changes: 4 additions & 0 deletions tests/std/tests/P1223R5_ranges_alg_find_last_if/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
71 changes: 71 additions & 0 deletions tests/std/tests/P1223R5_ranges_alg_find_last_if/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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;
using P = pair<int, int>;

constexpr auto matches = [](const int val) { return val == 42; };
constexpr auto equals = [](auto x) { return [x](auto&& y) { return y == x; }; };

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::find_last_if(borrowed<false>{}, matches)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::find_last_if(borrowed<true>{}, matches)), ranges::subrange<int*>>);

template <class T, class U>
constexpr void check_value(const T& found, const U& value) {
if constexpr (same_as<T, P>) {
assert(found.first == value);
} else {
assert(found.peek().first == value);
}
}

struct instantiator {
static constexpr P haystack[3] = {{0, 42}, {2, 42}, {4, 42}};

template <ranges::forward_range Read>
static constexpr void call() {
using ranges::find_last_if, ranges::common_range, ranges::iterator_t;

for (const auto& [value, _] : haystack) {
{ // Validate range overload [found case]
Read wrapped_input{haystack};
auto result = find_last_if(wrapped_input, equals(value), get_first);
STATIC_ASSERT(same_as<iterator_t<decltype(result)>, iterator_t<Read>>);
STATIC_ASSERT(common_range<decltype(result)>);
check_value(result.front(), value);
}
{ // Validate iterator + sentinel overload [found case]
Read wrapped_input{haystack};
auto result = find_last_if(wrapped_input.begin(), wrapped_input.end(), equals(value), get_first);
STATIC_ASSERT(same_as<iterator_t<decltype(result)>, iterator_t<Read>>);
STATIC_ASSERT(common_range<decltype(result)>);
check_value(result.front(), value);
}
}
{ // Validate range overload [not found case]
Read wrapped_input{haystack};
auto result = find_last_if(wrapped_input, equals(42), get_first);
STATIC_ASSERT(same_as<iterator_t<decltype(result)>, iterator_t<Read>>);
assert(result.begin() == wrapped_input.end());
}
{ // Validate iterator + sentinel overload [not found case]
Read wrapped_input{haystack};
auto result = find_last_if(wrapped_input.begin(), wrapped_input.end(), equals(42), get_first);
STATIC_ASSERT(same_as<iterator_t<decltype(result)>, iterator_t<Read>>);
assert(result.begin() == wrapped_input.end());
}
}
};

int main() {
STATIC_ASSERT((test_fwd<instantiator, const P>(), true));
test_fwd<instantiator, const P>();
}
Loading