//===----------------------------------------------------------------------===//
//
// 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;
}