| //===----------------------------------------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // <algorithm> |
| |
| // UNSUPPORTED: c++03, c++11, c++14, c++17 |
| |
| // template<class T, class Proj = identity, |
| // indirect_strict_weak_order<projected<const T*, Proj>> Comp = ranges::less> |
| // constexpr ranges::minmax_result<const T&> |
| // ranges::minmax(const T& a, const T& b, Comp comp = {}, Proj proj = {}); |
| // template<copyable T, class Proj = identity, |
| // indirect_strict_weak_order<projected<const T*, Proj>> Comp = ranges::less> |
| // constexpr ranges::minmax_result<T> |
| // ranges::minmax(initializer_list<T> r, Comp comp = {}, Proj proj = {}); |
| // template<input_range R, class Proj = identity, |
| // indirect_strict_weak_order<projected<iterator_t<R>, Proj>> Comp = ranges::less> |
| // requires indirectly_copyable_storable<iterator_t<R>, range_value_t<R>*> |
| // constexpr ranges::minmax_result<range_value_t<R>> |
| // ranges::minmax(R&& r, Comp comp = {}, Proj proj = {}); |
| |
| #include <algorithm> |
| #include <array> |
| #include <cassert> |
| #include <functional> |
| #include <memory> |
| #include <ranges> |
| #include <string> |
| |
| #include "test_iterators.h" |
| |
| template <class T> |
| concept HasMinMax = requires { std::ranges::minmax(std::declval<T>()); }; |
| |
| struct NoLessThanOp {}; |
| struct NotTotallyOrdered { |
| int i; |
| bool operator<(const NotTotallyOrdered& o) const { return i < o.i; } |
| }; |
| struct MoveOnly { |
| MoveOnly(MoveOnly&&) = default; |
| MoveOnly& operator=(MoveOnly&&) = default; |
| MoveOnly(const MoveOnly&) = delete; |
| }; |
| |
| static_assert(!HasMinMax<int>); |
| |
| static_assert(HasMinMax<int (&)[10]>); // make sure HasMinMax works with an array |
| static_assert(!HasMinMax<NoLessThanOp (&)[10]>); |
| static_assert(!HasMinMax<NotTotallyOrdered (&)[10]>); |
| static_assert(!HasMinMax<MoveOnly (&)[10]>); |
| |
| static_assert(HasMinMax<std::initializer_list<int>>); // make sure HasMinMax works with an initializer_list |
| static_assert(!HasMinMax<std::initializer_list<NoLessThanOp>>); |
| static_assert(!HasMinMax<std::initializer_list<NotTotallyOrdered>>); |
| static_assert(!HasMinMax<std::initializer_list<MoveOnly>>); |
| |
| static_assert(std::is_same_v<std::ranges::minmax_result<int>, std::ranges::min_max_result<int>>); |
| |
| static_assert(std::is_same_v<decltype(std::ranges::minmax(1, 2)), std::ranges::minmax_result<const int&>>); |
| |
| constexpr void test_2_arguments() { |
| const int one = 1; |
| const int two = 2; |
| { |
| auto result = std::ranges::minmax(one, two); |
| assert(result.min == 1); |
| assert(result.max == 2); |
| } |
| |
| { |
| auto result = std::ranges::minmax(two, one); |
| assert(result.min == 1); |
| assert(result.max == 2); |
| } |
| |
| { |
| // test comparator |
| auto result = std::ranges::minmax(one, two, std::ranges::greater{}); |
| assert(result.min == 2); |
| assert(result.max == 1); |
| } |
| |
| { |
| // test projection |
| auto result = std::ranges::minmax(one, two, std::ranges::less{}, [](int i) { return i == 1 ? 10 : i; }); |
| assert(result.min == 2); |
| assert(result.max == 1); |
| } |
| |
| { |
| // test if std::invoke is used for the predicate |
| struct S { |
| int i; |
| }; |
| S a[3] = {S{2}, S{1}, S{3}}; |
| std::same_as<std::ranges::minmax_result<const S&>> auto ret = std::ranges::minmax(a[0], a[1], {}, &S::i); |
| assert(&ret.min == &a[1]); |
| assert(&ret.max == &a[0]); |
| assert(ret.min.i == 1); |
| assert(ret.max.i == 2); |
| } |
| |
| { |
| // check that std::invoke is used for the comparator |
| struct S { |
| int i; |
| constexpr bool comp(S rhs) const { return i < rhs.i; } |
| }; |
| S a[] = {{2}, {5}}; |
| auto ret = std::ranges::minmax(a[0], a[1], &S::comp); |
| assert(ret.min.i == 2); |
| assert(ret.max.i == 5); |
| } |
| |
| { |
| // make sure that {a, b} is returned if b is not less than a |
| auto r = std::ranges::minmax(one, two); |
| assert(&r.min == &one); |
| assert(&r.max == &two); |
| } |
| } |
| |
| constexpr void test_initializer_list() { |
| { |
| // test projection |
| auto proj = [](int i) { return i == 5 ? -100 : i; }; |
| auto ret = std::ranges::minmax({7, 6, 9, 3, 5, 1, 2, 4}, std::ranges::less{}, proj); |
| assert(ret.min == 5); |
| assert(ret.max == 9); |
| } |
| |
| { |
| // test comparator |
| auto ret = std::ranges::minmax({7, 6, 9, 3, 5, 1, 2, 4}, std::ranges::greater{}); |
| assert(ret.min == 9); |
| assert(ret.max == 1); |
| } |
| |
| { |
| // check predicate and projection call counts |
| 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<std::ranges::minmax_result<int>> auto ret = std::ranges::minmax({1, 2, 3}, comparator, projection); |
| assert(ret.min == 1); |
| assert(ret.max == 3); |
| assert(compares == 3); |
| assert(projections == 6); |
| } |
| |
| { |
| // check that std::invoke is used for the predicate |
| struct S { |
| int i; |
| }; |
| std::same_as<std::ranges::minmax_result<S>> auto ret = std::ranges::minmax({S{2}, S{1}, S{3}}, {}, &S::i); |
| assert(ret.min.i == 1); |
| assert(ret.max.i == 3); |
| } |
| |
| { |
| // check that std::invoke is used for the comparator |
| struct S { |
| int i; |
| constexpr bool comp(S rhs) const { return i < rhs.i; } |
| }; |
| auto ret = std::ranges::minmax({S {1}, S {2}, S {3}, S {4}}, &S::comp); |
| assert(ret.min.i == 1); |
| assert(ret.max.i == 4); |
| } |
| |
| { |
| // check that a single element works |
| auto ret = std::ranges::minmax({ 1 }); |
| assert(ret.min == 1); |
| assert(ret.max == 1); |
| } |
| |
| { |
| // check in ascending order |
| auto ret = std::ranges::minmax({1, 2, 3}); |
| assert(ret.min == 1); |
| assert(ret.max == 3); |
| } |
| |
| { |
| // check in descending order |
| auto ret = std::ranges::minmax({3, 2, 1}); |
| assert(ret.min == 1); |
| assert(ret.max == 3); |
| } |
| } |
| |
| template <class Iter, class Sent = Iter> |
| constexpr void test_range_types() { |
| { |
| // test projection |
| int a[] = {7, 6, 9, 3, 5, 1, 2, 4}; |
| auto proj = [](int& i) { return i == 5 ? -100 : i; }; |
| auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 8))); |
| auto ret = std::ranges::minmax(range, std::ranges::less{}, proj); |
| assert(ret.min == 5); |
| assert(ret.max == 9); |
| } |
| |
| { |
| // test comparator |
| int a[] = {7, 6, 9, 3, 5, 1, 2, 4}; |
| auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 8))); |
| auto ret = std::ranges::minmax(range, std::ranges::greater{}); |
| assert(ret.min == 9); |
| assert(ret.max == 1); |
| } |
| |
| { |
| // check that complexity requirements are met |
| int compares = 0; |
| int projections = 0; |
| auto comparator = [&](int x, int y) { |
| ++compares; |
| return x < y; |
| }; |
| auto projection = [&](int x) { |
| ++projections; |
| return x; |
| }; |
| int a[] = {1, 2, 3}; |
| auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 3))); |
| std::same_as<std::ranges::minmax_result<int>> auto ret = std::ranges::minmax(range, comparator, projection); |
| assert(ret.min == 1); |
| assert(ret.max == 3); |
| assert(compares == 3); |
| assert(projections == 6); |
| } |
| |
| { |
| // check that a single element works |
| int a[] = { 1 }; |
| auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 1))); |
| auto ret = std::ranges::minmax(range); |
| assert(ret.min == 1); |
| assert(ret.max == 1); |
| } |
| |
| { |
| // check in ascending order |
| int a[] = {1, 2, 3}; |
| auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 3))); |
| auto ret = std::ranges::minmax(range); |
| assert(ret.min == 1); |
| assert(ret.max == 3); |
| } |
| |
| { |
| // check in descending order |
| int a[] = {3, 2, 1}; |
| auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 3))); |
| auto ret = std::ranges::minmax(range); |
| assert(ret.min == 1); |
| assert(ret.max == 3); |
| } |
| } |
| |
| constexpr void test_range() { |
| test_range_types<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>(); |
| test_range_types<forward_iterator<int*>>(); |
| test_range_types<bidirectional_iterator<int*>>(); |
| test_range_types<random_access_iterator<int*>>(); |
| test_range_types<contiguous_iterator<int*>>(); |
| |
| { |
| // check that std::invoke is used for the predicate |
| struct S { |
| int i; |
| }; |
| S b[3] = {S{2}, S{1}, S{3}}; |
| std::same_as<std::ranges::minmax_result<S>> auto ret = std::ranges::minmax(b, {}, &S::i); |
| assert(ret.min.i == 1); |
| assert(ret.max.i == 3); |
| } |
| |
| { |
| // check that std::invoke is used for the comparator |
| struct S { |
| int i; |
| constexpr bool comp(S rhs) const { return i < rhs.i; } |
| }; |
| S a[] = {{1}, {2}, {3}, {4}}; |
| auto ret = std::ranges::minmax(a, &S::comp); |
| assert(ret.min.i == 1); |
| assert(ret.max.i == 4); |
| } |
| |
| { |
| // check that the leftmost value for min an rightmost for max are returned |
| struct S { |
| int comp; |
| int other; |
| }; |
| S a[] = { {0, 0}, {0, 2}, {0, 1} }; |
| auto ret = std::ranges::minmax(a, {}, &S::comp); |
| assert(ret.min.comp == 0); |
| assert(ret.max.comp == 0); |
| assert(ret.min.other == 0); |
| assert(ret.max.other == 1); |
| } |
| |
| { |
| // check that an rvalue array works |
| int a[] = {1, 2, 3, 4}; |
| auto ret = std::ranges::minmax(std::move(a)); |
| assert(ret.min == 1); |
| assert(ret.max == 4); |
| } |
| { |
| // check that the input iterator isn't moved from multiple times |
| const std::string str{"this long string will be dynamically allocated"}; |
| std::string a[] = {str}; |
| auto range = std::ranges::subrange( |
| cpp20_input_iterator(std::move_iterator(a)), sentinel_wrapper(cpp20_input_iterator(std::move_iterator(a + 1)))); |
| auto ret = std::ranges::minmax(range); |
| assert(ret.min == str); |
| assert(ret.max == str); |
| } |
| { |
| // check that the forward iterator isn't moved from multiple times |
| const std::string str{"this long string will be dynamically allocated"}; |
| std::string a[] = {str}; |
| auto range = |
| std::ranges::subrange(forward_iterator(std::move_iterator(a)), forward_iterator(std::move_iterator(a + 1))); |
| auto ret = std::ranges::minmax(range); |
| assert(ret.min == str); |
| assert(ret.max == str); |
| } |
| } |
| |
| constexpr bool test() { |
| test_2_arguments(); |
| test_initializer_list(); |
| test_range(); |
| |
| return true; |
| } |
| |
| int main(int, char**) { |
| test(); |
| static_assert(test()); |
| |
| return 0; |
| } |