From 6c977f513a0d1f57197ea8ffb47e2d9dadd93f5b Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Fri, 19 Jun 2020 18:36:49 -0700 Subject: [PATCH 1/4] Implement ranges min/max/minmax algorithms: * `ranges::min` (direct, `range`, and `initializer_list` overloads) * `ranges::max` (direct, `range`, and `initializer_list` overloads) * `ranges::minmax` (direct, `range`, and `initializer_list` overloads) * `ranges::min_element` (`range` only) * `ranges::max_element` (`range` only) * `ranges::minmax_element` (`range` only) * `ranges::clamp` (direct only) Implements concept `indirectly_copyable_storable`, and adds test coverage for both that concept and `indirectly_movable_storable`. --- stl/inc/algorithm | 439 +++++++++++++++++- stl/inc/xutility | 11 + tests/std/include/range_algorithm_support.hpp | 330 +++++++------ tests/std/test.lst | 1 + .../tests/P0896R4_ranges_alg_minmax/env.lst | 4 + .../tests/P0896R4_ranges_alg_minmax/test.cpp | 352 ++++++++++++++ .../test.cpp | 146 ++++-- 7 files changed, 1100 insertions(+), 183 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_minmax/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_minmax/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 049e11eb817..bd24b77a5b9 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -5314,6 +5314,54 @@ _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) { + 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 +5405,54 @@ _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) { + 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, _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, _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 +5525,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 = _Minmax_element_unchecked( + _Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)), _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _Second = _First; + _Seek_wrapped(_First, _STD move(_UResult.min)); + _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 = _Minmax_element_unchecked( + _Get_unwrapped(_STD move(_First)), _Uend(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); + auto _Second = _First; + _Seek_wrapped(_First, _STD move(_UResult.min)); + _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 +5631,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 const 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 +5711,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 const 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 +5806,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 const 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)"); + 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>(*_Found.min), static_cast>(*_Found.max)}; + } else { + minmax_result> _Found = { + static_cast>(*_UFirst), static_cast>(*_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) { @@ -5662,8 +6068,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 45f60c53788..433d5fd67b3 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_rvalue_reference_t<_In>> + && assignable_from&, iter_rvalue_reference_t<_In>>; // clang-format on // CUSTOMIZATION POINT OBJECT iter_swap diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index ff25541c317..1f12ef6431f 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -62,8 +62,8 @@ struct boolish { }; namespace test { - using std::assignable_from, std::conditional_t, std::copy_constructible, std::derived_from, std::exchange, - std::ptrdiff_t, std::span; + using std::assignable_from, std::conditional_t, std::convertible_to, std::copy_constructible, std::derived_from, + std::exchange, std::ptrdiff_t, std::span; using output = std::output_iterator_tag; using input = std::input_iterator_tag; @@ -120,11 +120,43 @@ namespace test { } }; + // clang-format off + template + concept CanEq = requires(T const& t, U const& u) { + { t == u } -> convertible_to; + }; + + template + concept CanNEq = requires(T const& t, U const& u) { + { t != u } -> convertible_to; + }; + + template + concept CanLt = requires(T const& t, U const& u) { + { t < u } -> convertible_to; + }; + + template + concept CanLtE = requires(T const& t, U const& u) { + { t <= u } -> convertible_to; + }; + + template + concept CanGt = requires(T const& t, U const& u) { + { t > u } -> convertible_to; + }; + + template + concept CanGtE = requires(T const& t, U const& u) { + { t >= u } -> convertible_to; + }; + // clang-format on + template class proxy_reference { Element& ref_; - using ValueType = std::remove_cv_t; + using Value = std::remove_cv_t; public: constexpr explicit proxy_reference(Element& ref) : ref_{ref} {} @@ -137,34 +169,85 @@ namespace test { } // clang-format off - constexpr operator ValueType() const requires derived_from && copy_constructible { + constexpr operator Value() const requires derived_from && copy_constructible { return ref_; } // clang-format on - constexpr void operator=(ValueType const& val) const requires assignable_from { + constexpr void operator=(Value const& val) const requires assignable_from { ref_ = val; } + template + constexpr boolish operator==(proxy_reference that) const requires CanEq { + return {ref_ == that.peek()}; + } + template + constexpr boolish operator!=(proxy_reference that) const requires CanNEq { + return {ref_ != that.peek()}; + } + template + constexpr boolish operator<(proxy_reference that) const requires CanLt { + return {ref_ < that.peek()}; + } + template + constexpr boolish operator>(proxy_reference that) const requires CanGt { + return {ref_ > that.peek()}; + } + template + constexpr boolish operator<=(proxy_reference that) const requires CanLtE { + return {ref_ <= that.peek()}; + } + template + constexpr boolish operator>=(proxy_reference that) const requires CanGtE { + return {ref_ >= that.peek()}; + } + + // clang-format off + friend constexpr boolish operator==(proxy_reference ref, Value const& val) requires CanEq { + return {ref.ref_ == val}; + } + friend constexpr boolish operator==(Value const& val, proxy_reference ref) requires CanEq { + return {ref.ref_ == val}; + } + friend constexpr boolish operator!=(proxy_reference ref, Value const& val) requires CanNEq { + return {ref.ref_ != val}; + } + friend constexpr boolish operator!=(Value const& val, proxy_reference ref) requires CanNEq { + return {ref.ref_ != val}; + } + friend constexpr boolish operator<(Value const& val, proxy_reference ref) requires CanLt { + return {val < ref.ref_}; + } + friend constexpr boolish operator<(proxy_reference ref, Value const& val) requires CanLt { + return {ref.ref_ < val}; + } + friend constexpr boolish operator>(Value const& val, proxy_reference ref) requires CanGt { + return {val > ref.ref_}; + } + friend constexpr boolish operator>(proxy_reference ref, Value const& val) requires CanGt { + return {ref.ref_ > val}; + } + friend constexpr boolish operator<=(Value const& val, proxy_reference ref) requires CanLtE { + return {val <= ref.ref_}; + } + friend constexpr boolish operator<=(proxy_reference ref, Value const& val) requires CanLtE { + return {ref.ref_ <= val}; + } + friend constexpr boolish operator>=(Value const& val, proxy_reference ref) requires CanGtE { + return {val >= ref.ref_}; + } + friend constexpr boolish operator>=(proxy_reference ref, Value const& val) requires CanGtE { + return {ref.ref_ >= val}; + } + // clang-format on + constexpr Element& peek() const noexcept { return ref_; } }; // clang-format off - template - constexpr boolish operator==(proxy_reference x, proxy_reference y) requires requires { - { x.peek() == y.peek() } -> std::convertible_to; - } { - return {x.peek() == y.peek()}; - } - template - constexpr boolish operator!=(proxy_reference x, proxy_reference y) requires requires { - { x.peek() == y.peek() } -> std::convertible_to; - } { - return !(x == y); - } - template }, @@ -566,75 +649,64 @@ struct with_writable_iterators { }; template -struct with_input_ranges { +struct with_contiguous_ranges { template static constexpr void call() { using namespace test; - // For all ranges, IsCommon implies Eq. - // For single-pass ranges, Eq is uninteresting without IsCommon (there's only one valid iterator - // value at a time, and no reason to compare it with itself for equality). - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); + // Ditto always Eq; !IsSized && SizedSentinel is uninteresting (ranges::size still works), as is + // !IsSized && IsCommon. contiguous also implies !Proxy. Continuation::template call>(); - + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); + } +}; - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); +template +struct with_random_ranges { + template + static constexpr void call() { + using namespace test; - // forward always has Eq; !IsSized && Diff is uninteresting (sized_range is sized_range). - Continuation::template call>(); - Continuation::template call>(); + // Ditto always Eq; !IsSized && SizedSentinel is uninteresting (ranges::size works either way), as is + // !IsSized && IsCommon. Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); + + with_contiguous_ranges::template call(); + } +}; + +template +struct with_bidirectional_ranges { + template + static constexpr void call() { + using namespace test; // Ditto always Eq; !IsSized && Diff is uninteresting (ranges::size still works). Continuation::template call>(); - // Ditto always Eq; !IsSized && SizedSentinel is uninteresting (ranges::size works either way), as is - // !IsSized && IsCommon. - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - - // Ditto always Eq; !IsSized && SizedSentinel is uninteresting (ranges::size still works), as is - // !IsSized && IsCommon. contiguous also implies !Proxy. - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); + with_random_ranges::template call(); } }; @@ -732,67 +770,56 @@ struct with_forward_ranges { Continuation::template call>(); - // Ditto always Eq; !IsSized && Diff is uninteresting (ranges::size still works). - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); - Continuation::template call>(); + with_bidirectional_ranges::template call(); + } +}; + +template +struct with_input_ranges { + template + static constexpr void call() { + using namespace test; + + // For all ranges, IsCommon implies Eq. + // For single-pass ranges, Eq is uninteresting without IsCommon (there's only one valid iterator + // value at a time, and no reason to compare it with itself for equality). Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); - // Ditto always Eq; !IsSized && SizedSentinel is uninteresting (ranges::size works either way), as is - // !IsSized && IsCommon. - Continuation::template call>(); Continuation::template call>(); - Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); + Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); - // Ditto always Eq; !IsSized && SizedSentinel is uninteresting (ranges::size still works), as is - // !IsSized && IsCommon. contiguous also implies !Proxy. - Continuation::template call>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); Continuation::template call>(); + range>(); + + with_forward_ranges::template call(); } }; @@ -853,6 +880,21 @@ constexpr void test_fwd() { with_forward_ranges::call(); } +template +constexpr void test_bidi() { + with_bidirectional_ranges::call(); +} + +template +constexpr void test_random() { + with_random_ranges::call(); +} + +template +constexpr void test_contiguous() { + with_contiguous_ranges::call(); +} + template constexpr void test_in_in() { with_input_ranges, Element1>::call(); diff --git a/tests/std/test.lst b/tests/std/test.lst index 8672000b159..1cb0e89be90 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -251,6 +251,7 @@ tests\P0896R4_ranges_alg_find_if_not tests\P0896R4_ranges_alg_for_each tests\P0896R4_ranges_alg_for_each_n tests\P0896R4_ranges_alg_is_permutation +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..eaec327b399 --- /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, or 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, or 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 192aed0c171..a262d6d18ea 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