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 @@ -15,11 +15,11 @@ Search,upper_bound,Christopher Di Bella,`D105795 `_,Under review Search,equal_range,Christopher Di Bella,n/a,Not started Search,binary_search,Christopher Di Bella,n/a,Not started -Search,min,Not assigned,n/a,Not started +Search,min,Nikolas Klauser,`D119589 `_,✅ Search,max,Not assigned,n/a,Not started Search,minmax,Not assigned,n/a,Not started -Search,min_element,Nikolas Klauser,n/a,✅ -Search,max_element,Not assigned,n/a,Not started +Search,min_element,Nikolas Klauser,`D117025 `_,✅ +Search,max_element,Nikolas Klauser,`D117523 `_,✅ Search,minmax_element,Not assigned,n/a,Not started Search,count,Not assigned,n/a,Not started Search,count_if,Not assigned,n/a,Not started diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -70,6 +70,7 @@ __algorithm/ranges_find_if.h __algorithm/ranges_find_if_not.h __algorithm/ranges_max_element.h + __algorithm/ranges_min.h __algorithm/ranges_min_element.h __algorithm/ranges_mismatch.h __algorithm/ranges_swap_ranges.h diff --git a/libcxx/include/__algorithm/ranges_min.h b/libcxx/include/__algorithm/ranges_min.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__algorithm/ranges_min.h @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// 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_MIN_H +#define _LIBCPP___ALGORITHM_RANGES_MIN_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 + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +#if !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace ranges { +namespace __min { +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, __b), std::invoke(__proj, __a)) ? __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"); + return *ranges::__min_element_impl(__il.begin(), __il.end(), __comp, __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>) { + return *ranges::__min_element_impl(__first, __last, __comp, __proj); + } else { + range_value_t<_Rp> __result = *__first; + while (++__first != __last) { + if (std::invoke(__comp, std::invoke(__proj, *__first), std::invoke(__proj, __result))) + __result = *__first; + } + return __result; + } + } +}; +} // namespace __min + +inline namespace __cpo { + inline constexpr auto min = __min::__fn{}; +} // namespace __cpo +} // namespace ranges + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) + +#endif // _LIBCPP___ALGORITHM_RANGES_MIN_H diff --git a/libcxx/include/__algorithm/ranges_min_element.h b/libcxx/include/__algorithm/ranges_min_element.h --- a/libcxx/include/__algorithm/ranges_min_element.h +++ b/libcxx/include/__algorithm/ranges_min_element.h @@ -29,33 +29,34 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace ranges { -namespace __min_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, *__i), std::invoke(__proj, *__first))) - __first = __i; +template +_LIBCPP_HIDE_FROM_ABI static constexpr +_Ip __min_element_impl(_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, *__i), std::invoke(__proj, *__first))) + __first = __i; + return __first; +} + +namespace __min_element { +struct __fn { 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); + return ranges::__min_element_impl(__first, __last, __comp, __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); + return ranges::__min_element_impl(ranges::begin(__r), ranges::end(__r), __comp, __proj); } }; } // namespace __min_element diff --git a/libcxx/include/__ranges/take_view.h b/libcxx/include/__ranges/take_view.h --- a/libcxx/include/__ranges/take_view.h +++ b/libcxx/include/__ranges/take_view.h @@ -9,7 +9,7 @@ #ifndef _LIBCPP___RANGES_TAKE_VIEW_H #define _LIBCPP___RANGES_TAKE_VIEW_H -#include <__algorithm/min.h> +#include <__algorithm/ranges_min.h> #include <__config> #include <__iterator/concepts.h> #include <__iterator/counted_iterator.h> @@ -118,15 +118,13 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto size() requires sized_range<_View> { auto __n = ranges::size(__base_); - // TODO: use ranges::min here. - return std::min(__n, static_cast(__count_)); + return ranges::min(__n, static_cast(__count_)); } _LIBCPP_HIDE_FROM_ABI constexpr auto size() const requires sized_range { auto __n = ranges::size(__base_); - // TODO: use ranges::min here. - return std::min(__n, static_cast(__count_)); + return ranges::min(__n, static_cast(__count_)); } }; diff --git a/libcxx/include/algorithm b/libcxx/include/algorithm --- a/libcxx/include/algorithm +++ b/libcxx/include/algorithm @@ -57,7 +57,6 @@ constexpr mismatch_result, borrowed_iterator_t> mismatch(R1&& r1, R2&& r2, Pred pred = {}, Proj1 proj1 = {}, Proj2 proj2 = {}) // since C++20 - template S, class T, class Proj = identity> requires indirect_binary_predicate, const T*> constexpr I find(I first, S last, const T& value, Proj proj = {}); // since C++20 @@ -83,9 +82,22 @@ indirect_unary_predicate, Proj>> Pred> 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 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 constexpr bool // constexpr in C++20 all_of(InputIterator first, InputIterator last, Predicate pred); @@ -804,6 +816,7 @@ #include <__algorithm/ranges_find_if.h> #include <__algorithm/ranges_find_if_not.h> #include <__algorithm/ranges_max_element.h> +#include <__algorithm/ranges_min.h> #include <__algorithm/ranges_min_element.h> #include <__algorithm/ranges_mismatch.h> #include <__algorithm/ranges_swap_ranges.h> diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -298,6 +298,7 @@ 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_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" } module ranges_mismatch { private header "__algorithm/ranges_mismatch.h" } module ranges_swap_ranges { private header "__algorithm/ranges_swap_ranges.h" } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/ranges_min.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/ranges_min.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/algorithm/ranges_min.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_min.h'}} +#include <__algorithm/ranges_min.h> diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/algorithms/alg.sorting/alg.min.max/ranges.min.pass.cpp @@ -0,0 +1,259 @@ +//===----------------------------------------------------------------------===// +// +// 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-no-concepts +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// template> Comp = ranges::less> +// constexpr const T& ranges::min(const T& a, const T& b, Comp comp = {}, Proj proj = {}); +// template> Comp = ranges::less> +// constexpr T ranges::min(initializer_list r, Comp comp = {}, Proj proj = {}); +// template, Proj>> Comp = ranges::less> +// requires indirectly_copyable_storable, range_value_t*> +// constexpr range_value_t +// ranges::min(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 HasMinR = requires { std::ranges::min(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(!HasMinR); + +static_assert(HasMinR); +static_assert(HasMinR); +static_assert(!HasMinR); +static_assert(!HasMinR); +static_assert(!HasMinR); + +static_assert(HasMinR>); +static_assert(!HasMinR>); +static_assert(!HasMinR>); +static_assert(!HasMinR>); +static_assert(!HasMinR); +static_assert(!HasMinR); +static_assert(!HasMinR); +static_assert(!HasMinR); +static_assert(!HasMinR); + +template +concept HasMin2 = requires { std::ranges::min(std::declval(), std::declval()); }; + +static_assert(HasMin2); +static_assert(!HasMin2); + +static_assert(std::is_same_v); + +constexpr void test_2_arguments() { + assert(std::ranges::min(1, 2) == 1); + assert(std::ranges::min(2, 1) == 1); + // test comparator + assert(std::ranges::min(1, 2, std::ranges::greater{}) == 2); + // test projection + assert(std::ranges::min(1, 2, std::ranges::less{}, [](int i){ return i == 1 ? 10 : i; }) == 2); + + { // check that std::invoke is used + struct S { int i; }; + S a[3] = { S{2}, S{1}, S{3} }; + decltype(auto) ret = std::ranges::min(a[0], a[1], {}, &S::i); + ASSERT_SAME_TYPE(decltype(ret), const S&); + assert(&ret == &a[1]); + assert(ret.i == 1); + } + + { // check that pointers are compared and not a range + int i; + int* a[] = {&i, &i + 1}; + auto ret = std::ranges::min(a[0], a[1]); + assert(ret == &i); + } + + { // 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::min(1, 2, comparator, projection); + assert(ret == 1); + assert(compares == 1); + assert(projections == 2); + } + + { // check that the first argument is returned + struct S { int check; int other; }; + auto ret = std::ranges::min(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::min({7, 6, 9, 3, 5, 1, 2, 4}, std::ranges::less{}, proj); + assert(ret == 5); + } + + { + // test comparator + int ret = std::ranges::min({7, 6, 9, 3, 5, 1, 2, 4}, std::ranges::greater{}); + assert(ret == 9); + } + + { + 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::min({1, 2, 3}, comparator, projection); + assert(ret == 1); + assert(compares == 2); + assert(projections == 4); + } + + { + struct S { int i; }; + decltype(auto) ret = std::ranges::min({ S{2}, S{1}, S{3} }, {}, &S::i); + ASSERT_SAME_TYPE(decltype(ret), S); + assert(ret.i == 1); + } + + { + int a[] = {7, 6, 9, 3, 5, 1, 2, 4}; + using It = cpp20_input_iterator; + using Sent = sentinel_wrapper; + auto range = std::ranges::subrange(It(a), Sent(It(a + 8))); + auto ret = std::ranges::min(range); + assert(ret == 1); + } +} + +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::min(range); + assert(ret == 1); +} + +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::min(a, std::ranges::less{}, proj); + assert(ret == 5); + } + + { + // test comparator + int ret = std::ranges::min(a, std::ranges::greater{}); + assert(ret == 9); + } + + { // 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::min(std::array{1, 2, 3}, comparator, projection); + assert(ret == 1); + 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::min(b, {}, &S::i); + assert(ret.i == 1); + } + + { // check that the first smallest element is returned + { // where the first element is the smallest + struct S { int check; int other; }; + S b[] = { S{0, 1}, S{1, 2}, S{0, 3} }; + auto ret = std::ranges::min(b, {}, &S::check); + assert(ret.check == 0); + assert(ret.other == 1); + } + { // where the first element isn't the smallest + struct S { int check; int other; }; + S b[] = { S{2, 1}, S{0, 2}, S{0, 3} }; + auto ret = std::ranges::min(b, {}, &S::check); + assert(ret.check == 0); + 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; +} diff --git a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp --- a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp +++ b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp @@ -99,7 +99,7 @@ //static_assert(test(std::ranges::max, a)); static_assert(test(std::ranges::max_element, a)); //static_assert(test(std::ranges::merge, a, a, a)); -//static_assert(test(std::ranges::min, a)); +static_assert(test(std::ranges::min, a)); static_assert(test(std::ranges::min_element, a)); //static_assert(test(std::ranges::minmax, a)); //static_assert(test(std::ranges::minmax_element, a));