diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 5ca15b9e767..a6ea8a759ff 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -5314,6 +5314,56 @@ _NODISCARD _FwdIt max_element(_ExPo&&, _FwdIt _First, _FwdIt _Last) noexcept /* // not parallelized at present, parallelism expected to be feasible in a future release return _STD max_element(_First, _Last); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::max_element + template + _NODISCARD constexpr _It _Max_element_unchecked(_It _First, const _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_strict_weak_order<_Pr, projected<_It, _Pj>>); + + auto _Found = _First; + if (_First == _Last) { + return _Found; + } + + while (++_First != _Last) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Found), _STD invoke(_Proj, *_First))) { + _Found = _First; + } + } + + return _Found; + } + + class _Max_element_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Pj = identity, + indirect_strict_weak_order> _Pr = ranges::less> + _NODISCARD constexpr _It operator()(_It _First, _Se _Last, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + _Seek_wrapped(_First, _RANGES _Max_element_unchecked(_Get_unwrapped(_STD move(_First)), + _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj))); + return _First; + } + + template , _Pj>> _Pr = ranges::less> + _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + _Seek_wrapped(_First, _RANGES _Max_element_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), + _Pass_fn(_Pred), _Pass_fn(_Proj))); + return _First; + } + }; + + inline constexpr _Max_element_fn max_element{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE min_element @@ -5357,6 +5407,56 @@ _NODISCARD _FwdIt min_element(_ExPo&&, _FwdIt _First, _FwdIt _Last) noexcept /* // not parallelized at present, parallelism expected to be feasible in a future release return _STD min_element(_First, _Last); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::min_element + template + _NODISCARD constexpr _It _Min_element_unchecked(_It _First, const _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_strict_weak_order<_Pr, projected<_It, _Pj>>); + + auto _Found = _First; + if (_First == _Last) { + return _Found; + } + + while (++_First != _Last) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First), _STD invoke(_Proj, *_Found))) { + _Found = _First; + } + } + + return _Found; + } + + class _Min_element_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Pj = identity, + indirect_strict_weak_order> _Pr = ranges::less> + _NODISCARD constexpr _It operator()(_It _First, _Se _Last, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + _Seek_wrapped(_First, _RANGES _Min_element_unchecked(_Get_unwrapped(_STD move(_First)), + _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj))); + return _First; + } + + template , _Pj>> _Pr = ranges::less> + _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + _Seek_wrapped(_First, _RANGES _Min_element_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range), + _Pass_fn(_Pred), _Pass_fn(_Proj))); + return _First; + } + }; + + inline constexpr _Min_element_fn min_element{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE minmax_element @@ -5429,6 +5529,96 @@ _NODISCARD pair<_FwdIt, _FwdIt> minmax_element(_ExPo&&, _FwdIt _First, _FwdIt _L // not parallelized at present, parallelism expected to be feasible in a future release return _STD minmax_element(_First, _Last); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE minmax_element_result + template + using minmax_element_result = min_max_result<_It>; + + // VARIABLE ranges::minmax_element + template + constexpr min_max_result<_It> _Minmax_element_unchecked(_It _First, const _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_strict_weak_order<_Pr, projected<_It, _Pj>>); + + min_max_result<_It> _Found{_First, _First}; + + if (_First == _Last) { + return _Found; + } + + while (++_First != _Last) { // process one or two elements + _It _Prev = _First; + if (++_First == _Last) { // process last element + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Prev), _STD invoke(_Proj, *_Found.min))) { + _Found.min = _Prev; + } else if (!_STD invoke(_Pred, _STD invoke(_Proj, *_Prev), _STD invoke(_Proj, *_Found.max))) { + _Found.max = _Prev; + } + + break; + } + + // process next two elements + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First), _STD invoke(_Proj, *_Prev))) { + // test _First for new smallest + if (_STD invoke(_Pred, _STD invoke(_Proj, *_First), _STD invoke(_Proj, *_Found.min))) { + _Found.min = _First; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_Prev), _STD invoke(_Proj, *_Found.max))) { + _Found.max = _Prev; + } + } else { // test _Prev for new smallest + if (_STD invoke(_Pred, _STD invoke(_Proj, *_Prev), _STD invoke(_Proj, *_Found.min))) { + _Found.min = _Prev; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_First), _STD invoke(_Proj, *_Found.max))) { + _Found.max = _First; + } + } + } + + return _Found; + } + + class _Minmax_element_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se, class _Pj = identity, + indirect_strict_weak_order> _Pr = ranges::less> + _NODISCARD constexpr minmax_element_result<_It> operator()( + _It _First, _Se _Last, _Pr _Pred = {}, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _RANGES _Minmax_element_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + _Seek_wrapped(_First, _STD move(_UResult.min)); + auto _Second = _First; + _Seek_wrapped(_Second, _STD move(_UResult.max)); + return {_STD move(_First), _STD move(_Second)}; + } + + template , _Pj>> _Pr = ranges::less> + _NODISCARD constexpr minmax_element_result> operator()( + _Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + auto _UResult = _RANGES _Minmax_element_unchecked( + _Get_unwrapped(_STD move(_First)), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + _Seek_wrapped(_First, _STD move(_UResult.min)); + auto _Second = _First; + _Seek_wrapped(_Second, _STD move(_UResult.max)); + return {_STD move(_First), _STD move(_Second)}; + } + }; + + inline constexpr _Minmax_element_fn minmax_element{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 // FUNCTION TEMPLATE max (for initializer_list) @@ -5445,6 +5635,72 @@ _NODISCARD constexpr _Ty(max)(initializer_list<_Ty> _Ilist) { return (_STD max)(_Ilist, less<>()); } +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::max + // clang-format off + template + concept _Prefer_iterator_copies = // When we have a choice, should we copy iterators or copy elements? + // pre: input_iterator<_It> + sizeof(_It) <= 2 * sizeof(iter_value_t<_It>) + && (is_trivially_copyable_v<_It> || !is_trivially_copyable_v>); + // clang-format on + + class _Max_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template > _Pr = ranges::less> + _NODISCARD constexpr const _Ty& operator()( + const _Ty& _Left, const _Ty& _Right, _Pr _Pred = {}, _Pj _Proj = {}) const { + if (_STD invoke(_Pred, _STD invoke(_Proj, _Left), _STD invoke(_Proj, _Right))) { + return _Right; + } else { + return _Left; + } + } + + template > _Pr = ranges::less> + _NODISCARD constexpr _Ty operator()(initializer_list<_Ty> _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + const auto _First = _Range.begin(); + const auto _Last = _Range.end(); + _STL_ASSERT(_First != _Last, + "An initializer_list passed to std::ranges::max must not be empty. (N4861 [alg.min.max]/13)"); + return *_RANGES _Max_element_unchecked(_First, _Last, _Pass_fn(_Pred), _Pass_fn(_Proj)); + } + + // clang-format off + template , _Pj>> _Pr = ranges::less> + requires indirectly_copyable_storable, range_value_t<_Rng>*> + _NODISCARD constexpr range_value_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _UFirst = _Ubegin(_Range); + auto _ULast = _Uend(_Range); + _STL_ASSERT( + _UFirst != _ULast, "A range passed to std::ranges::max must not be empty. (N4861 [alg.min.max]/13)"); + if constexpr (forward_range<_Rng> && _Prefer_iterator_copies>) { + return static_cast>(*_RANGES _Max_element_unchecked( + _STD move(_UFirst), _STD move(_ULast), _Pass_fn(_Pred), _Pass_fn(_Proj))); + } else { + range_value_t<_Rng> _Found(*_UFirst); + while (++_UFirst != _ULast) { + if (_STD invoke(_Pred, _STD invoke(_Proj, _Found), _STD invoke(_Proj, *_UFirst))) { + _Found = *_UFirst; + } + } + + return _Found; + } + } + // clang-format on + }; + + inline constexpr _Max_fn max{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE min (for initializer_list) template _NODISCARD constexpr _Ty(min)(initializer_list<_Ty> _Ilist, _Pr _Pred) { @@ -5459,6 +5715,64 @@ _NODISCARD constexpr _Ty(min)(initializer_list<_Ty> _Ilist) { return (_STD min)(_Ilist, less<>()); } +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::min + class _Min_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template > _Pr = ranges::less> + _NODISCARD constexpr const _Ty& operator()( + const _Ty& _Left, const _Ty& _Right, _Pr _Pred = {}, _Pj _Proj = {}) const { + if (_STD invoke(_Pred, _STD invoke(_Proj, _Right), _STD invoke(_Proj, _Left))) { + return _Right; + } else { + return _Left; + } + } + + template > _Pr = ranges::less> + _NODISCARD constexpr _Ty operator()(initializer_list<_Ty> _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + const auto _First = _Range.begin(); + const auto _Last = _Range.end(); + _STL_ASSERT(_First != _Last, + "An initializer_list passed to std::ranges::min must not be empty. (N4861 [alg.min.max]/5)"); + return *_RANGES _Min_element_unchecked(_First, _Last, _Pass_fn(_Pred), _Pass_fn(_Proj)); + } + + // clang-format off + template , _Pj>> _Pr = ranges::less> + requires indirectly_copyable_storable, range_value_t<_Rng>*> + _NODISCARD constexpr range_value_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _UFirst = _Ubegin(_Range); + auto _ULast = _Uend(_Range); + _STL_ASSERT( + _UFirst != _ULast, "A range passed to std::ranges::min must not be empty. (N4861 [alg.min.max]/5)"); + if constexpr (forward_range<_Rng> && _Prefer_iterator_copies>) { + return static_cast>(*_RANGES _Min_element_unchecked( + _STD move(_UFirst), _STD move(_ULast), _Pass_fn(_Pred), _Pass_fn(_Proj))); + } else { + range_value_t<_Rng> _Found(*_UFirst); + while (++_UFirst != _ULast) { + if (_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst), _STD invoke(_Proj, _Found))) { + _Found = *_UFirst; + } + } + + return _Found; + } + } + // clang-format on + }; + + inline constexpr _Min_fn min{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE minmax template _NODISCARD constexpr pair minmax(const _Ty& _Left, const _Ty& _Right, _Pr _Pred) noexcept( @@ -5496,6 +5810,102 @@ _NODISCARD constexpr pair<_Ty, _Ty> minmax(initializer_list<_Ty> _Ilist) { return _STD minmax(_Ilist, less<>()); } +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE minmax_result + template + using minmax_result = min_max_result<_Ty>; + + // VARIABLE ranges::minmax + class _Minmax_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template > _Pr = ranges::less> + _NODISCARD constexpr minmax_result operator()( + const _Ty& _Left, const _Ty& _Right, _Pr _Pred = {}, _Pj _Proj = {}) const { + if (_STD invoke(_Pred, _STD invoke(_Proj, _Right), _STD invoke(_Proj, _Left))) { + return {_Right, _Left}; + } else { + return {_Left, _Right}; + } + } + + template > _Pr = ranges::less> + _NODISCARD constexpr minmax_result<_Ty> operator()( + initializer_list<_Ty> _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + const auto _First = _Range.begin(); + const auto _Last = _Range.end(); + _STL_ASSERT(_First != _Last, + "An initializer_list passed to std::ranges::minmax must not be empty. (N4861 [alg.min.max]/21)"); + const auto _Found = _RANGES _Minmax_element_unchecked(_First, _Last, _Pass_fn(_Pred), _Pass_fn(_Proj)); + return {static_cast<_Ty>(*_Found.min), static_cast<_Ty>(*_Found.max)}; + } + + template , _Pj>> _Pr = ranges::less> + requires indirectly_copyable_storable, range_value_t<_Rng>*> + _NODISCARD constexpr minmax_result> operator()( + _Rng&& _Range, _Pr _Pred = {}, _Pj _Proj = {}) const { + auto _UFirst = _Ubegin(_Range); + auto _ULast = _Uend(_Range); + _STL_ASSERT( + _UFirst != _ULast, "A range passed to std::ranges::minmax must not be empty. (N4861 [alg.min.max]/21)"); + using _Vty = range_value_t<_Rng>; + if constexpr (forward_range<_Rng> && _Prefer_iterator_copies>) { + const auto _Found = _RANGES _Minmax_element_unchecked( + _STD move(_UFirst), _STD move(_ULast), _Pass_fn(_Pred), _Pass_fn(_Proj)); + return {static_cast<_Vty>(*_Found.min), static_cast<_Vty>(*_Found.max)}; + } else { + minmax_result<_Vty> _Found = {static_cast<_Vty>(*_UFirst), static_cast<_Vty>(*_UFirst)}; + if (_UFirst == _ULast) { + return _Found; + } + + while (++_UFirst != _ULast) { // process one or two elements + auto _Prev = *_UFirst; + if (++_UFirst == _ULast) { // process last element + if (_STD invoke(_Pred, _STD invoke(_Proj, _Prev), _STD invoke(_Proj, _Found.min))) { + _Found.min = _STD move(_Prev); + } else if (!_STD invoke(_Pred, _STD invoke(_Proj, _Prev), _STD invoke(_Proj, _Found.max))) { + _Found.max = _STD move(_Prev); + } + + break; + } + + // process next two elements + if (_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst), _STD invoke(_Proj, _Prev))) { + // test _UFirst for new smallest + if (_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst), _STD invoke(_Proj, _Found.min))) { + _Found.min = *_UFirst; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, _Prev), _STD invoke(_Proj, _Found.max))) { + _Found.max = _STD move(_Prev); + } + } else { // test _Prev for new smallest + if (_STD invoke(_Pred, _STD invoke(_Proj, _Prev), _STD invoke(_Proj, _Found.min))) { + _Found.min = _STD move(_Prev); + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj, *_UFirst), _STD invoke(_Proj, _Found.max))) { + _Found.max = *_UFirst; + } + } + } + + return _Found; + } + } + }; + + inline constexpr _Minmax_fn minmax{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE next_permutation template _CONSTEXPR20 bool next_permutation(_BidIt _First, _BidIt _Last, _Pr _Pred) { @@ -5737,8 +6147,39 @@ _NODISCARD constexpr const _Ty& clamp(const _Ty& _Val, const _Ty& _Min_val, cons template _NODISCARD constexpr const _Ty& clamp(const _Ty& _Val, const _Ty& _Min_val, const _Ty& _Max_val) { // returns _Val constrained to [_Min_val, _Max_val] - return _STD clamp(_Val, _Min_val, _Max_val, less<>()); + return _STD clamp(_Val, _Min_val, _Max_val, less{}); } + +#ifdef __cpp_lib_concepts +namespace ranges { + // VARIABLE ranges::clamp + class _Clamp_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template > _Pr = ranges::less> + _NODISCARD constexpr const _Ty& operator()( + const _Ty& _Val, const _Ty& _Lo, const _Ty& _Hi, _Pr _Pred = {}, _Pj _Proj = {}) const { + _STL_ASSERT(!_STD invoke(_Pred, _STD invoke(_Proj, _Hi), _STD invoke(_Proj, _Lo)), + "The lower bound cannot be greater than the upper bound in a call to std::ranges::clamp " + "(N4861 [alg.clamp]/2)."); + + if (_STD invoke(_Pred, _STD invoke(_Proj, _Val), _STD invoke(_Proj, _Lo))) { + return _Lo; + } + + if (_STD invoke(_Pred, _STD invoke(_Proj, _Hi), _STD invoke(_Proj, _Val))) { + return _Hi; + } + + return _Val; + } + }; + + inline constexpr _Clamp_fn clamp{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts #endif // _HAS_CXX17 _STD_END diff --git a/stl/inc/xutility b/stl/inc/xutility index a52f68cc5b8..ae6413103be 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -990,6 +990,17 @@ concept indirectly_movable_storable = indirectly_movable<_In, _Out> // CONCEPT indirectly_copyable template concept indirectly_copyable = indirectly_readable<_In> && indirectly_writable<_Out, iter_reference_t<_In>>; + +// CONCEPT indirectly_copyable_storable +template +concept indirectly_copyable_storable = indirectly_copyable<_In, _Out> + && indirectly_writable<_Out, iter_value_t<_In>&> + && indirectly_writable<_Out, const iter_value_t<_In>&> + && indirectly_writable<_Out, iter_value_t<_In>&&> + && indirectly_writable<_Out, const iter_value_t<_In>&&> + && copyable> + && constructible_from, iter_reference_t<_In>> + && assignable_from&, iter_reference_t<_In>>; // clang-format on // CUSTOMIZATION POINT OBJECT iter_swap diff --git a/tests/std/test.lst b/tests/std/test.lst index d3ebf442847..6e282af8beb 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -253,6 +253,7 @@ tests\P0896R4_ranges_alg_for_each tests\P0896R4_ranges_alg_for_each_n tests\P0896R4_ranges_alg_is_permutation tests\P0896R4_ranges_alg_is_sorted +tests\P0896R4_ranges_alg_minmax tests\P0896R4_ranges_alg_mismatch tests\P0896R4_ranges_alg_move tests\P0896R4_ranges_alg_none_of diff --git a/tests/std/tests/P0896R4_ranges_alg_minmax/env.lst b/tests/std/tests/P0896R4_ranges_alg_minmax/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/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_minmax/test.cpp b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp new file mode 100644 index 00000000000..e76461db2e2 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp @@ -0,0 +1,352 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Covers ranges::min, ranges::max, ranges::minmax, ranges::clamp, ranges::min_element, ranges::max_element, and +// ranges::minmax_element + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +#define ASSERT(...) assert((__VA_ARGS__)) + +// Validate dangling story +STATIC_ASSERT(same_as{})), ranges::dangling>); +STATIC_ASSERT(same_as{})), int*>); + +STATIC_ASSERT(same_as{})), ranges::dangling>); +STATIC_ASSERT(same_as{})), int*>); + +STATIC_ASSERT( + same_as{})), ranges::minmax_element_result>); +STATIC_ASSERT(same_as{})), ranges::minmax_element_result>); + +// Validate that minmax_result and minmax_element_result alias min_max_result +STATIC_ASSERT(same_as, ranges::min_max_result>); +STATIC_ASSERT(same_as, ranges::min_max_result>); + +using P = pair; + +struct mm_element_empty { + template + static constexpr void call() { + // Validate empty ranges + const Fwd range{}; + + ASSERT(ranges::min_element(range, ranges::less{}, get_first) == ranges::end(range)); + ASSERT(ranges::min_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first) + == ranges::end(range)); + + ASSERT(ranges::max_element(range, ranges::less{}, get_first) == ranges::end(range)); + ASSERT(ranges::max_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first) + == ranges::end(range)); + + { + auto result = ranges::minmax_element(range, ranges::less{}, get_first); + STATIC_ASSERT(same_as>>); + ASSERT(result.min == ranges::end(range)); + ASSERT(result.max == ranges::end(range)); + } + { + auto result = ranges::minmax_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first); + STATIC_ASSERT(same_as>>); + ASSERT(result.min == ranges::end(range)); + ASSERT(result.max == ranges::end(range)); + } + } +}; + +static constexpr array pairs = {P{5, 0}, P{5, 1}, P{5, 2}, P{5, 3}, P{5, 4}, P{5, 5}, P{5, 6}, P{5, 7}}; + +struct mm_element { + template + static constexpr void call() { + constexpr auto N = pairs.size(); + auto elements = pairs; + const Fwd range{elements}; + + ASSERT(*ranges::min_element(range, ranges::less{}, get_first) == P{5, 0}); + ASSERT(*ranges::min_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first) == P{5, 0}); + + ASSERT(*ranges::max_element(range, ranges::less{}, get_first) == P{5, 0}); + ASSERT(*ranges::max_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first) == P{5, 0}); + + { + auto result = ranges::minmax_element(range, ranges::less{}, get_first); + ASSERT(*result.min == P{5, 0}); + ASSERT(*result.max == P{5, 7}); + } + { + auto result = ranges::minmax_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first); + ASSERT(*result.min == P{5, 0}); + ASSERT(*result.max == P{5, 7}); + } + + for (size_t i = 0; i < N; ++i) { + elements[i] = P{0, 42}; + + ASSERT(*ranges::min_element(range, ranges::less{}, get_first) == P{0, 42}); + ASSERT( + *ranges::min_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first) == P{0, 42}); + + ASSERT(*ranges::max_element(range, ranges::less{}, get_first) == (i == 0 ? P{5, 1} : P{5, 0})); + ASSERT(*ranges::max_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first) + == (i == 0 ? P{5, 1} : P{5, 0})); + + { + const auto result = ranges::minmax_element(range, ranges::less{}, get_first); + ASSERT(*result.min == P{0, 42}); + ASSERT(*result.max == (i == 7 ? P{5, 6} : P{5, 7})); + } + { + const auto result = + ranges::minmax_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first); + ASSERT(*result.min == P{0, 42}); + ASSERT(*result.max == (i == 7 ? P{5, 6} : P{5, 7})); + } + + for (size_t j = i + 1; j < N; ++j) { + elements[j] = P{0, 13}; + + ASSERT(*ranges::min_element(range, ranges::less{}, get_first) == P{0, 42}); + ASSERT(*ranges::min_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first) + == P{0, 42}); + + ASSERT(*ranges::max_element(range, ranges::less{}, get_first) + == (i == 0 ? (j == 1 ? P{5, 2} : P{5, 1}) : P{5, 0})); + ASSERT(*ranges::max_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first) + == (i == 0 ? (j == 1 ? P{5, 2} : P{5, 1}) : P{5, 0})); + + { + const auto result = ranges::minmax_element(range, ranges::less{}, get_first); + ASSERT(*result.min == P{0, 42}); + ASSERT(*result.max == (j == 7 ? (i == 6 ? P{5, 5} : P{5, 6}) : P{5, 7})); + } + { + const auto result = + ranges::minmax_element(ranges::begin(range), ranges::end(range), ranges::less{}, get_first); + ASSERT(*result.min == P{0, 42}); + ASSERT(*result.max == (j == 7 ? (i == 6 ? P{5, 5} : P{5, 6}) : P{5, 7})); + } + + elements[j] = P{5, static_cast(j)}; + } + + elements[i] = P{5, static_cast(i)}; + } + } +}; + +struct mm { + template + static constexpr void call() { + constexpr auto N = pairs.size(); + auto elements = pairs; + + ASSERT(ranges::min(In{elements}, ranges::less{}, get_first) == P{5, 0}); + ASSERT(ranges::max(In{elements}, ranges::less{}, get_first) == P{5, 0}); + { + auto result = ranges::minmax(In{elements}, ranges::less{}, get_first); + ASSERT(result.min == P{5, 0}); + ASSERT(result.max == P{5, 7}); + } + + for (size_t i = 0; i < N; ++i) { + elements[i] = P{0, 42}; + + ASSERT(ranges::min(In{elements}, ranges::less{}, get_first) == P{0, 42}); + ASSERT(ranges::max(In{elements}, ranges::less{}, get_first) == (i == 0 ? P{5, 1} : P{5, 0})); + + { + auto result = ranges::minmax(In{elements}, ranges::less{}, get_first); + STATIC_ASSERT(same_as>); + ASSERT(result.min == P{0, 42}); + ASSERT(result.max == (i == 7 ? P{5, 6} : P{5, 7})); + } + + for (size_t j = i + 1; j < N; ++j) { + elements[j] = P{0, 13}; + + ASSERT(ranges::min(In{elements}, ranges::less{}, get_first) == P{0, 42}); + + ASSERT(ranges::max(In{elements}, ranges::less{}, get_first) + == (i == 0 ? (j == 1 ? P{5, 2} : P{5, 1}) : P{5, 0})); + + { + auto result = ranges::minmax(In{elements}, ranges::less{}, get_first); + STATIC_ASSERT(same_as>); + ASSERT(result.min == P{0, 42}); + ASSERT(result.max == (j == 7 ? (i == 6 ? P{5, 5} : P{5, 6}) : P{5, 7})); + } + + elements[j] = P{5, static_cast(j)}; + } + + elements[i] = P{5, static_cast(i)}; + } + } +}; + +constexpr void nonrange_tests() { + // validate overloads of ranges::min, ranges::max, ranges::minmax, and ranges::clamp which take values directly + + constexpr auto thirteen = 13; + constexpr auto also_thirteen = thirteen; + constexpr auto forty_two = 42; + constexpr auto thirteen_pair = P{13, 200}; + constexpr auto also_thirteen_pair = thirteen_pair; + constexpr auto forty_two_pair = P{42, 100}; + + ASSERT(&ranges::min(thirteen, forty_two) == &thirteen); + ASSERT(&ranges::min(forty_two, thirteen) == &thirteen); + ASSERT(&ranges::min(thirteen, also_thirteen) == &thirteen); + + ASSERT(&ranges::min(thirteen, forty_two, ranges::greater{}) == &forty_two); + ASSERT(&ranges::min(forty_two, thirteen, ranges::greater{}) == &forty_two); + ASSERT(&ranges::min(thirteen, also_thirteen, ranges::greater{}) == &thirteen); + + ASSERT(&ranges::min(thirteen_pair, forty_two_pair, ranges::greater{}, get_first) == &forty_two_pair); + ASSERT(&ranges::min(forty_two_pair, thirteen_pair, ranges::greater{}, get_first) == &forty_two_pair); + ASSERT(&ranges::min(thirteen_pair, P{thirteen_pair}, ranges::greater{}, get_first) == &thirteen_pair); + + ASSERT(&ranges::max(thirteen, forty_two) == &forty_two); + ASSERT(&ranges::max(forty_two, thirteen) == &forty_two); + ASSERT(&ranges::max(thirteen, also_thirteen) == &thirteen); + + ASSERT(&ranges::max(thirteen, forty_two, ranges::greater{}) == &thirteen); + ASSERT(&ranges::max(forty_two, thirteen, ranges::greater{}) == &thirteen); + ASSERT(&ranges::max(thirteen, also_thirteen, ranges::greater{}) == &thirteen); + + ASSERT(&ranges::max(thirteen_pair, forty_two_pair, ranges::greater{}, get_first) == &thirteen_pair); + ASSERT(&ranges::max(forty_two_pair, thirteen_pair, ranges::greater{}, get_first) == &thirteen_pair); + ASSERT(&ranges::max(thirteen_pair, P{thirteen_pair}, ranges::greater{}, get_first) == &thirteen_pair); + + ASSERT(&ranges::minmax(thirteen, forty_two).min == &thirteen); + ASSERT(&ranges::minmax(thirteen, forty_two).max == &forty_two); + ASSERT(&ranges::minmax(forty_two, thirteen).min == &thirteen); + ASSERT(&ranges::minmax(forty_two, thirteen).max == &forty_two); + ASSERT(&ranges::minmax(thirteen, also_thirteen).min == &thirteen); + ASSERT(&ranges::minmax(thirteen, also_thirteen).max == &also_thirteen); + + ASSERT(&ranges::minmax(thirteen, forty_two, ranges::greater{}).min == &forty_two); + ASSERT(&ranges::minmax(thirteen, forty_two, ranges::greater{}).max == &thirteen); + ASSERT(&ranges::minmax(forty_two, thirteen, ranges::greater{}).min == &forty_two); + ASSERT(&ranges::minmax(forty_two, thirteen, ranges::greater{}).max == &thirteen); + ASSERT(&ranges::minmax(thirteen, also_thirteen, ranges::greater{}).min == &thirteen); + ASSERT(&ranges::minmax(thirteen, also_thirteen, ranges::greater{}).max == &also_thirteen); + + ASSERT(&ranges::minmax(thirteen_pair, forty_two_pair, ranges::greater{}, get_first).min == &forty_two_pair); + ASSERT(&ranges::minmax(thirteen_pair, forty_two_pair, ranges::greater{}, get_first).max == &thirteen_pair); + ASSERT(&ranges::minmax(forty_two_pair, thirteen_pair, ranges::greater{}, get_first).min == &forty_two_pair); + ASSERT(&ranges::minmax(forty_two_pair, thirteen_pair, ranges::greater{}, get_first).max == &thirteen_pair); + ASSERT(&ranges::minmax(thirteen_pair, also_thirteen_pair, ranges::greater{}, get_first).min == &thirteen_pair); + ASSERT(&ranges::minmax(thirteen_pair, also_thirteen_pair, ranges::greater{}, get_first).max == &also_thirteen_pair); + + constexpr auto also_forty_two = 42; + constexpr auto less_than_thirteen = 11; + constexpr auto between_thirteen_and_forty_two = 37; + constexpr auto greater_than_forty_two = 43; + constexpr auto also_forty_two_pair = P{42, 100}; + constexpr auto less_than_thirteen_pair = P{11, 250}; + constexpr auto between_thirteen_and_forty_two_pair = P{37, 150}; + constexpr auto greater_than_forty_two_pair = P{43, 50}; + + ASSERT(&ranges::clamp(less_than_thirteen, thirteen, forty_two) == &thirteen); + ASSERT(&ranges::clamp(also_thirteen, thirteen, forty_two) == &also_thirteen); + ASSERT(&ranges::clamp(between_thirteen_and_forty_two, thirteen, forty_two) == &between_thirteen_and_forty_two); + ASSERT(&ranges::clamp(also_forty_two, thirteen, forty_two) == &also_forty_two); + ASSERT(&ranges::clamp(greater_than_forty_two, thirteen, forty_two) == &forty_two); + + + ASSERT(&ranges::clamp(less_than_thirteen_pair, forty_two_pair, thirteen_pair, ranges::greater{}, get_first) + == &thirteen_pair); + ASSERT(&ranges::clamp(also_thirteen_pair, forty_two_pair, thirteen_pair, ranges::greater{}, get_first) + == &also_thirteen_pair); + ASSERT( + &ranges::clamp(between_thirteen_and_forty_two_pair, forty_two_pair, thirteen_pair, ranges::greater{}, get_first) + == &between_thirteen_and_forty_two_pair); + ASSERT(&ranges::clamp(also_forty_two_pair, forty_two_pair, thirteen_pair, ranges::greater{}, get_first) + == &also_forty_two_pair); + ASSERT(&ranges::clamp(greater_than_forty_two_pair, forty_two_pair, thirteen_pair, ranges::greater{}, get_first) + == &forty_two_pair); +} + +constexpr void init_list_constexpr_tests() { + ASSERT( + ranges::min({P{5, 0}, P{5, 1}, P{5, 2}, P{5, 3}, P{5, 4}, P{5, 5}, P{5, 6}, P{5, 7}}, ranges::less{}, get_first) + == P{5, 0}); + ASSERT( + ranges::max({P{5, 0}, P{5, 1}, P{5, 2}, P{5, 3}, P{5, 4}, P{5, 5}, P{5, 6}, P{5, 7}}, ranges::less{}, get_first) + == P{5, 0}); + auto result = ranges::minmax( + {P{5, 0}, P{5, 1}, P{5, 2}, P{5, 3}, P{5, 4}, P{5, 5}, P{5, 6}, P{5, 7}}, ranges::less{}, get_first); + STATIC_ASSERT(same_as>); + ASSERT(result.min == P{5, 0}); + ASSERT(result.max == P{5, 7}); +} + +constexpr void mm_element_constexpr_tests() { + // (min|max|minmax)_element don't care about size, iterator difference, refinements of forward, commonality, + // _or_ proxy vs. non-proxy reference. Let's take a couple variations of forward, and one + // variation of each stronger category. + + using test::range, test::Sized, test::CanDifference, test::Common, test::CanCompare, test::ProxyRef, test::fwd; + using E = const P; + + mm_element::call>(); + mm_element::call>(); + + mm_element::call>(); + mm_element::call>(); + mm_element::call>(); +} + +constexpr void mm_constexpr_tests() { + // Range overloads of (min|max|minmax) don't care about size, iterator difference, commonality, _or_ proxy vs. + // non-proxy reference. They _do_ distinguish input vs. forward. Let's take a couple variations of input and + // forward, and one variation of each stronger category. + + using test::range, test::Sized, test::CanDifference, test::Common, test::CanCompare, test::ProxyRef, test::input, + test::fwd; + using E = const P; + + mm::call>(); + mm::call>(); + + mm::call>(); + mm::call>(); + + mm::call>(); + mm::call>(); + mm::call>(); +} + +int main() { + STATIC_ASSERT((nonrange_tests(), true)); + nonrange_tests(); + + STATIC_ASSERT((init_list_constexpr_tests(), true)); + init_list_constexpr_tests(); + + STATIC_ASSERT((test_fwd(), true)); + test_fwd(); + + STATIC_ASSERT((mm_element_constexpr_tests(), true)); + test_fwd(); + + STATIC_ASSERT((mm_constexpr_tests(), true)); + test_in(); +} diff --git a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp index dcf15858bb2..b049409901c 100644 --- a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp @@ -46,34 +46,35 @@ struct borrowed { // borrowed is a borrowed_range; borrowed is not template <> inline constexpr bool std::ranges::enable_borrowed_range> = true; +template struct simple_reference { - operator int() const; + operator T() const; }; +template struct simple_common_reference { - simple_common_reference(simple_reference); - simple_common_reference(int); + simple_common_reference(simple_reference); + simple_common_reference(T const&); }; -template +template struct simple_iter_archetype { - using value_type = int; + using value_type = T; - // 0: not indirectly_readable - simple_reference operator*() const requires(I != 0); + simple_reference operator*() const requires(I != 0); // 0: not indirectly_readable friend void iter_swap(simple_iter_archetype const&, simple_iter_archetype const&) {} }; STATIC_ASSERT(!std::indirectly_readable>); STATIC_ASSERT(std::indirectly_readable>); -template