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