diff --git a/libcxx/docs/Status/RangesAlgorithms.csv b/libcxx/docs/Status/RangesAlgorithms.csv --- a/libcxx/docs/Status/RangesAlgorithms.csv +++ b/libcxx/docs/Status/RangesAlgorithms.csv @@ -7,7 +7,7 @@ Search,find_if_not,Nikolas Klauser,`D121248 `_,✅ Search,find_first_of,Not assigned,n/a,Not started Search,adjacent_find,Not assigned,n/a,Not started -Search,mismatch,Nikolas Klauser,`D117817 `,Complete +Search,mismatch,Nikolas Klauser,`D117817 `_,✅ Search,equal,Not assigned,n/a,Not started Search,lexicographical_compare,Not assigned,n/a,Not started Search,partition_point,Christopher Di Bella,`D105794 `_,Under review @@ -16,7 +16,7 @@ Search,equal_range,Christopher Di Bella,n/a,Not started Search,binary_search,Christopher Di Bella,n/a,Not started Search,min,Nikolas Klauser,`D119589 `_,✅ -Search,max,Not assigned,n/a,Not started +Search,max,Nikolas Klauser,`D122002 `_,✅ Search,minmax,Not assigned,n/a,Not started Search,min_element,Nikolas Klauser,`D117025 `_,✅ Search,max_element,Nikolas Klauser,`D117523 `_,✅ diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -69,6 +69,7 @@ __algorithm/ranges_find.h __algorithm/ranges_find_if.h __algorithm/ranges_find_if_not.h + __algorithm/ranges_max.h __algorithm/ranges_max_element.h __algorithm/ranges_min.h __algorithm/ranges_min_element.h diff --git a/libcxx/include/__algorithm/ranges_max.h b/libcxx/include/__algorithm/ranges_max.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__algorithm/ranges_max.h @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___ALGORITHM_RANGES_MAX_H +#define _LIBCPP___ALGORITHM_RANGES_MAX_H + +#include <__algorithm/ranges_min_element.h> +#include <__assert> +#include <__concepts/copyable.h> +#include <__config> +#include <__functional/identity.h> +#include <__functional/invoke.h> +#include <__functional/ranges_operations.h> +#include <__iterator/concepts.h> +#include <__iterator/projected.h> +#include <__ranges/access.h> +#include <__ranges/concepts.h> +#include <__utility/move.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace ranges { +namespace __max { +struct __fn { + template > _Comp = ranges::less> + _LIBCPP_HIDE_FROM_ABI constexpr + const _Tp& operator()(const _Tp& __a, const _Tp& __b, _Comp __comp = {}, _Proj __proj = {}) const { + return std::invoke(__comp, std::invoke(__proj, __a), std::invoke(__proj, __b)) ? __b : __a; + } + + template > _Comp = ranges::less> + _LIBCPP_HIDE_FROM_ABI constexpr + _Tp operator()(initializer_list<_Tp> __il, _Comp __comp = {}, _Proj __proj = {}) const { + _LIBCPP_ASSERT(__il.begin() != __il.end(), "initializer_list must contain at least one element"); + + auto __comp_lhs_rhs_swapped = [&](auto&& __lhs, auto&& __rhs) { return std::invoke(__comp, __rhs, __lhs); }; + return *ranges::__min_element_impl(__il.begin(), __il.end(), __comp_lhs_rhs_swapped, __proj); + } + + template , _Proj>> _Comp = ranges::less> + requires indirectly_copyable_storable, range_value_t<_Rp>*> + _LIBCPP_HIDE_FROM_ABI constexpr + range_value_t<_Rp> operator()(_Rp&& __r, _Comp __comp = {}, _Proj __proj = {}) const { + auto __first = ranges::begin(__r); + auto __last = ranges::end(__r); + + _LIBCPP_ASSERT(__first != __last, "range must contain at least one element"); + + if constexpr (forward_range<_Rp>) { + auto __comp_lhs_rhs_swapped = [&](auto&& __lhs, auto&& __rhs) { return std::invoke(__comp, __rhs, __lhs); }; + return *ranges::__min_element_impl(std::move(__first), std::move(__last), __comp_lhs_rhs_swapped, __proj); + } else { + range_value_t<_Rp> __result = *__first; + while (++__first != __last) { + if (std::invoke(__comp, std::invoke(__proj, __result), std::invoke(__proj, *__first))) + __result = *__first; + } + return __result; + } + } +}; +} // namespace __max + +inline namespace __cpo { + inline constexpr auto max = __max::__fn{}; +} // namespace __cpo +} // namespace ranges + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STD_VER > 17 && && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) + +#endif // _LIBCPP___ALGORITHM_RANGES_MAX_H diff --git a/libcxx/include/__algorithm/ranges_max_element.h b/libcxx/include/__algorithm/ranges_max_element.h --- a/libcxx/include/__algorithm/ranges_max_element.h +++ b/libcxx/include/__algorithm/ranges_max_element.h @@ -9,6 +9,7 @@ #ifndef _LIBCPP___ALGORITHM_RANGES_MAX_ELEMENT_H #define _LIBCPP___ALGORITHM_RANGES_MAX_ELEMENT_H +#include <__algorithm/ranges_min_element.h> #include <__config> #include <__functional/identity.h> #include <__functional/invoke.h> @@ -30,31 +31,20 @@ namespace ranges { namespace __max_element { struct __fn { - template - _LIBCPP_HIDE_FROM_ABI static constexpr - _Ip __go(_Ip __first, _Sp __last, _Comp& __comp, _Proj& __proj) { - if (__first == __last) - return __first; - - _Ip __i = __first; - while (++__i != __last) - if (std::invoke(__comp, std::invoke(__proj, *__first), std::invoke(__proj, *__i))) - __first = __i; - return __first; - } - template _Sp, class _Proj = identity, indirect_strict_weak_order> _Comp = ranges::less> _LIBCPP_HIDE_FROM_ABI constexpr _Ip operator()(_Ip __first, _Sp __last, _Comp __comp = {}, _Proj __proj = {}) const { - return __go(__first, __last, __comp, __proj); + auto __comp_lhs_rhs_swapped = [&](auto&& __lhs, auto&& __rhs) { return std::invoke(__comp, __rhs, __lhs); }; + return __min_element_impl(__first, __last, __comp_lhs_rhs_swapped, __proj); } template , _Proj>> _Comp = ranges::less> _LIBCPP_HIDE_FROM_ABI constexpr borrowed_iterator_t<_Rp> operator()(_Rp&& __r, _Comp __comp = {}, _Proj __proj = {}) const { - return __go(ranges::begin(__r), ranges::end(__r), __comp, __proj); + auto __comp_lhs_rhs_swapped = [&](auto&& __lhs, auto&& __rhs) { return std::invoke(__comp, __rhs, __lhs); }; + return __min_element_impl(ranges::begin(__r), ranges::end(__r), __comp_lhs_rhs_swapped, __proj); } }; } // namespace __max_element diff --git a/libcxx/include/algorithm b/libcxx/include/algorithm --- a/libcxx/include/algorithm +++ b/libcxx/include/algorithm @@ -83,19 +83,33 @@ constexpr borrowed_iterator_t find_if_not(R&& r, Pred pred, Proj proj = {}); // since C++20 - template> Comp = ranges::less> - constexpr const T& min(const T& a, const T& b, Comp comp = {}, Proj proj = {}); // since C++20 + template> Comp = ranges::less> + constexpr const T& min(const T& a, const T& b, Comp comp = {}, Proj proj = {}); // since C++20 - template> Comp = ranges::less> - constexpr T min(initializer_list r, Comp comp = {}, Proj proj = {}); // since C++20 + template> Comp = ranges::less> + constexpr T min(initializer_list r, Comp comp = {}, Proj proj = {}); // since C++20 template, Proj>> Comp = ranges::less> requires indirectly_copyable_storable, range_value_t*> constexpr range_value_t min(R&& r, Comp comp = {}, Proj proj = {}); // since C++20 + + template> Comp = ranges::less> + constexpr const T& max(const T& a, const T& b, Comp comp = {}, Proj proj = {}); // since C++20 + + template> Comp = ranges::less> + constexpr T max(initializer_list r, Comp comp = {}, Proj proj = {}); // since C++20 + + template, Proj>> Comp = ranges::less> + requires indirectly_copyable_storable, range_value_t*> + constexpr range_value_t + max(R&& r, Comp comp = {}, Proj proj = {}); // since C++20 } constexpr bool // constexpr in C++20 @@ -815,6 +829,7 @@ #include <__algorithm/ranges_find.h> #include <__algorithm/ranges_find_if.h> #include <__algorithm/ranges_find_if_not.h> +#include <__algorithm/ranges_max.h> #include <__algorithm/ranges_max_element.h> #include <__algorithm/ranges_min.h> #include <__algorithm/ranges_min_element.h> diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -297,6 +297,7 @@ module ranges_find { private header "__algorithm/ranges_find.h" } module ranges_find_if { private header "__algorithm/ranges_find_if.h" } module ranges_find_if_not { private header "__algorithm/ranges_find_if_not.h" } + module ranges_max { private header "__algorithm/ranges_max.h" } module ranges_max_element { private header "__algorithm/ranges_max_element.h" } module ranges_min { private header "__algorithm/ranges_min.h" } module ranges_min_element { private header "__algorithm/ranges_min_element.h" } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/ranges_max.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/ranges_max.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/ranges_max.module.verify.cpp @@ -0,0 +1,15 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_max.h'}} +#include <__algorithm/ranges_max.h> diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.max.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.max.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.max.pass.cpp @@ -0,0 +1,261 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// template> Comp = ranges::less> +// constexpr const T& ranges::max(const T& a, const T& b, Comp comp = {}, Proj proj = {}); +// +// template> Comp = ranges::less> +// constexpr T ranges::max(initializer_list r, Comp comp = {}, Proj proj = {}); +// +// template, Proj>> Comp = ranges::less> +// requires indirectly_copyable_storable, range_value_t*> +// constexpr range_value_t +// ranges::max(R&& r, Comp comp = {}, Proj proj = {}); + +#include +#include +#include +#include + +#include "almost_satisfies_types.h" +#include "test_iterators.h" +#include "test_macros.h" + +template +concept HasMaxR = requires { std::ranges::max(std::declval()); }; + +struct NoLessThanOp {}; +struct NotTotallyOrdered { + int i; + bool operator<(const NotTotallyOrdered& o) const { return i < o.i; } +}; + +struct Movable { + Movable& operator=(Movable&&) = default; + Movable(Movable&&) = default; + Movable(const Movable&) = delete; +}; + +static_assert(!HasMaxR); + +static_assert(HasMaxR); +static_assert(HasMaxR); +static_assert(!HasMaxR); +static_assert(!HasMaxR); +static_assert(!HasMaxR); + +static_assert(HasMaxR>); +static_assert(!HasMaxR>); +static_assert(!HasMaxR>); +static_assert(!HasMaxR>); +static_assert(!HasMaxR); +static_assert(!HasMaxR); +static_assert(!HasMaxR); +static_assert(!HasMaxR); +static_assert(!HasMaxR); + +template +concept HasMax2 = requires { std::ranges::max(std::declval(), std::declval()); }; + +static_assert(HasMax2); +static_assert(!HasMax2); + +static_assert(std::is_same_v); + +constexpr void test_2_arguments() { + assert(std::ranges::max(1, 2) == 2); + assert(std::ranges::max(2, 1) == 2); + // test comparator + assert(std::ranges::max(1, 2, std::ranges::greater{}) == 1); + // test projection + assert(std::ranges::max(1, 2, std::ranges::less{}, [](int i){ return i == 1 ? 10 : i; }) == 1); + + { // check that std::invoke is used + struct S { int i; }; + S a[3] = { S{2}, S{1}, S{3} }; + decltype(auto) ret = std::ranges::max(a[0], a[1], {}, &S::i); + ASSERT_SAME_TYPE(decltype(ret), const S&); + assert(&ret == &a[0]); + assert(ret.i == 2); + } + + { // check that pointers are compared and not a range + int i[1]; + int* a[] = {i, i + 1}; + auto ret = std::ranges::max(a[0], a[1]); + assert(ret == i + 1); + } + + { // test predicate and projection count + int compares = 0; + int projections = 0; + auto comparator = [&](int x, int y) { + ++compares; + return x < y; + }; + auto projection = [&](int x) { + ++projections; + return x; + }; + auto ret = std::ranges::max(1, 2, comparator, projection); + assert(ret == 2); + assert(compares == 1); + assert(projections == 2); + } + + { // check that the first argument is returned + struct S { int check; int other; }; + auto ret = std::ranges::max(S {0, 1}, S {0, 2}, {}, &S::check); + assert(ret.other == 1); + } +} + +constexpr void test_initializer_list() { + { // test projection + auto proj = [](int i) { return i == 5 ? 100 : i; }; + int ret = std::ranges::max({7, 6, 9, 3, 5, 1, 2, 4}, {}, proj); + assert(ret == 5); + } + + { // test comparator + int ret = std::ranges::max({7, 6, 9, 3, 5, 1, 2, 4}, std::ranges::greater{}); + assert(ret == 1); + } + + { // check that complexity requirements are met + int compares = 0; + int projections = 0; + auto comparator = [&](int a, int b) { + ++compares; + return a < b; + }; + auto projection = [&](int a) { + ++projections; + return a; + }; + std::same_as decltype(auto) ret = std::ranges::max({1, 2, 3}, comparator, projection); + assert(ret == 3); + assert(compares == 2); + assert(projections == 4); + } + + { // check that std::invoke is used + struct S { int i; }; + std::same_as decltype(auto) ret = std::ranges::max({ S{2}, S{1}, S{3} }, {}, &S::i); + assert(ret.i == 3); + } + + { // check that the first largest element is returned + { // where the first element is the largest + struct S { int check; int other; }; + auto ret = std::ranges::max({ S{1, 1}, S{0, 2}, S{1, 3} }, {}, &S::check); + assert(ret.check == 1); + assert(ret.other == 1); + } + { // where the first element isn't the largest + struct S { int check; int other; }; + auto ret = std::ranges::max({ S{0, 1}, S{1, 2}, S{1, 3} }, {}, &S::check); + assert(ret.check == 1); + assert(ret.other == 2); + } + } +} + +template +constexpr void test_range_types() { + int a[] = {7, 6, 9, 3, 5, 1, 2, 4}; + auto range = std::ranges::subrange(It(a), Sent(It(a + 8))); + int ret = std::ranges::max(range); + assert(ret == 9); +} + +constexpr void test_range() { + { // check that all range types work + test_range_types, sentinel_wrapper>>(); + test_range_types>(); + test_range_types>(); + test_range_types>(); + test_range_types>(); + } + + int a[] = {7, 6, 9, 3, 5, 1, 2, 4}; + { // test projection + auto proj = [](int& i) { return i == 5 ? 100 : i; }; + int ret = std::ranges::max(a, std::ranges::less{}, proj); + assert(ret == 5); + } + + { // test comparator + int ret = std::ranges::max(a, std::ranges::greater{}); + assert(ret == 1); + } + + { // check that predicate and projection call counts are correct + int compares = 0; + int projections = 0; + auto comparator = [&](int x, int y) { + ++compares; + return x < y; + }; + auto projection = [&](int x) { + ++projections; + return x; + }; + std::same_as decltype(auto) ret = std::ranges::max(std::array{1, 2, 3}, comparator, projection); + assert(ret == 3); + assert(compares == 2); + assert(projections == 4); + } + + { // check that std::invoke is used + struct S { int i; }; + S b[3] = { S{2}, S{1}, S{3} }; + std::same_as decltype(auto) ret = std::ranges::max(b, {}, &S::i); + assert(ret.i == 3); + } + + { // check that the first largest element is returned + { // where the first element is the largest + struct S { int check; int other; }; + S b[] = { S{1, 1}, S{0, 2}, S{1, 3} }; + auto ret = std::ranges::max(b, {}, &S::check); + assert(ret.check == 1); + assert(ret.other == 1); + } + { // where the first element isn't the largest + struct S { int check; int other; }; + S b[] = { S{0, 1}, S{1, 2}, S{1, 3} }; + auto ret = std::ranges::max(b, {}, &S::check); + assert(ret.check == 1); + assert(ret.other == 2); + } + } +} + +constexpr bool test() { + test_2_arguments(); + test_initializer_list(); + test_range(); + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +}