1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 // <algorithm>
10
11 // UNSUPPORTED: c++03, c++11, c++14, c++17
12 // UNSUPPORTED: libcpp-has-no-incomplete-ranges
13
14 // Older Clangs don't properly deduce decltype(auto) with a concept constraint
15 // XFAIL: apple-clang-13.0
16
17 // template<class T, class Proj = identity,
18 // indirect_strict_weak_order<projected<const T*, Proj>> Comp = ranges::less>
19 // constexpr const T&
20 // ranges::clamp(const T& v, const T& lo, const T& hi, Comp comp = {}, Proj proj = {});
21
22 #include <algorithm>
23 #include <cassert>
24 #include <functional>
25 #include <utility>
26
27 template <class T, class Comp = std::ranges::less, class Proj = std::identity>
28 concept HasClamp =
29 requires(T&& val, T&& low, T&& high, Comp&& comp, Proj&& proj) {
30 std::ranges::clamp(std::forward<T>(val), std::forward<T>(low), std::forward<T>(high),
31 std::forward<Comp>(comp), std::forward<Proj>(proj));
32 };
33
34 struct NoComp {};
35 struct CreateNoComp {
operator ()CreateNoComp36 auto operator()(int) const { return NoComp(); }
37 };
38
39 static_assert(HasClamp<int, std::ranges::less, std::identity>);
40 static_assert(!HasClamp<NoComp>);
41 static_assert(!HasClamp<int, NoComp>);
42 static_assert(!HasClamp<int, std::ranges::less, CreateNoComp>);
43
test()44 constexpr bool test() {
45 { // low < val < high
46 int val = 2;
47 int low = 1;
48 int high = 3;
49 std::same_as<const int&> decltype(auto) ret = std::ranges::clamp(val, low, high);
50 assert(ret == 2);
51 assert(&ret == &val);
52 }
53
54 { // low > val < high
55 assert(std::ranges::clamp(10, 20, 30) == 20);
56 }
57
58 { // low < val > high
59 assert(std::ranges::clamp(15, 5, 10) == 10);
60 }
61
62 { // low == val == high
63 int val = 10;
64 assert(&std::ranges::clamp(val, 10, 10) == &val);
65 }
66
67 { // Check that a custom comparator works.
68 assert(std::ranges::clamp(10, 30, 20, std::ranges::greater{}) == 20);
69 }
70
71 { // Check that a custom projection works.
72 struct S {
73 int i;
74
75 constexpr const int& lvalue_proj() const { return i; }
76 constexpr int prvalue_proj() const { return i; }
77 };
78
79 struct Comp {
80 constexpr bool operator()(const int& lhs, const int& rhs) const { return lhs < rhs; }
81 constexpr bool operator()(int&& lhs, int&& rhs) const { return lhs > rhs; }
82 };
83
84 auto val = S{10};
85 auto low = S{20};
86 auto high = S{30};
87 // Check that the value category of the projection return type is preserved.
88 assert(&std::ranges::clamp(val, low, high, Comp{}, &S::lvalue_proj) == &low);
89 assert(&std::ranges::clamp(val, high, low, Comp{}, &S::prvalue_proj) == &low);
90 }
91
92 { // Check that the implementation doesn't cause double moves (which could result from calling the projection on
93 // `value` once and then forwarding the result into the comparator).
94 struct CheckDoubleMove {
95 int i;
96 bool moved = false;
97
98 constexpr explicit CheckDoubleMove(int set_i) : i(set_i) {}
99 constexpr CheckDoubleMove(const CheckDoubleMove&) = default;
100 constexpr CheckDoubleMove(CheckDoubleMove&& rhs) noexcept : i(rhs.i) {
101 assert(!rhs.moved);
102 rhs.moved = true;
103 }
104 };
105
106 auto val = CheckDoubleMove{20};
107 auto low = CheckDoubleMove{10};
108 auto high = CheckDoubleMove{30};
109
110 auto moving_comp = [](CheckDoubleMove lhs, CheckDoubleMove rhs) { return lhs.i < rhs.i; };
111 auto prvalue_proj = [](const CheckDoubleMove& x) -> CheckDoubleMove { return x; };
112 assert(&std::ranges::clamp(val, low, high, moving_comp, prvalue_proj) == &val);
113 }
114
115 return true;
116 }
117
main(int,char **)118 int main(int, char**) {
119 test();
120 static_assert(test());
121
122 return 0;
123 }
124