19
19
#include < cassert>
20
20
#include < concepts>
21
21
#include < functional>
22
+ #include < iterator>
22
23
#include < utility>
23
24
24
25
template <class T , class Comp = std::ranges::less, class Proj = std::identity>
@@ -38,6 +39,16 @@ static_assert(!HasClamp<NoComp>);
38
39
static_assert (!HasClamp<int , NoComp>);
39
40
static_assert (!HasClamp<int , std::ranges::less, CreateNoComp>);
40
41
42
+ struct EnsureValueCategoryComp {
43
+ constexpr bool operator ()(const int && x, const int && y) const { return x < y; }
44
+ constexpr bool operator ()(const int && x, int & y) const { return x < y; }
45
+ constexpr bool operator ()(int & x, const int && y) const { return x < y; }
46
+ constexpr bool operator ()(int & x, int & y) const { return x < y; }
47
+ constexpr bool operator ()(std::same_as<const int &> auto && x, std::same_as<const int &> auto && y) const {
48
+ return x < y;
49
+ }
50
+ };
51
+
41
52
constexpr bool test () {
42
53
{ // low < val < high
43
54
int val = 2 ;
@@ -68,45 +79,38 @@ constexpr bool test() {
68
79
{ // Check that a custom projection works.
69
80
struct S {
70
81
int i;
71
-
72
- constexpr const int & lvalue_proj () const { return i; }
73
- constexpr int prvalue_proj () const { return i; }
74
- };
75
-
76
- struct Comp {
77
- constexpr bool operator ()(const int & lhs, const int & rhs) const { return lhs < rhs; }
78
- constexpr bool operator ()(int && lhs, int && rhs) const { return lhs > rhs; }
82
+ constexpr bool operator ==(S const & other) const { return i == other.i ; }
79
83
};
80
84
81
85
auto val = S{10 };
82
86
auto low = S{20 };
83
87
auto high = S{30 };
84
- // Check that the value category of the projection return type is preserved.
85
- assert (& std::ranges::clamp (val, low, high, Comp{}, &S::lvalue_proj) == &low);
86
- assert (& std::ranges::clamp (val, high, low, Comp {}, &S::prvalue_proj ) == & low);
88
+ auto proj = [](S const & s) -> int const & { return s. i ; };
89
+
90
+ assert (std::ranges::clamp (val, low, high, std::less {}, proj ) == low);
87
91
}
88
92
89
- { // Check that the implementation doesn't cause double moves (which could result from calling the projection on
90
- // `value` once and then forwarding the result into the comparator).
91
- struct CheckDoubleMove {
92
- int i;
93
- bool moved = false ;
94
-
95
- constexpr explicit CheckDoubleMove (int set_i) : i(set_i) {}
96
- constexpr CheckDoubleMove (const CheckDoubleMove&) = default;
97
- constexpr CheckDoubleMove (CheckDoubleMove&& rhs) noexcept : i(rhs.i) {
98
- assert (!rhs.moved );
99
- rhs.moved = true ;
100
- }
93
+ { // Ensure that we respect the value category of the projection when calling the comparator.
94
+ // This additional example was provided by Tim Song in https://github.com/microsoft/STL/issues/3970#issuecomment-1685120958.
95
+ struct MoveProj {
96
+ constexpr int const && operator ()(int const & x) const { return std::move (x); }
101
97
};
102
98
103
- auto val = CheckDoubleMove{20 };
104
- auto low = CheckDoubleMove{10 };
105
- auto high = CheckDoubleMove{30 };
99
+ static_assert (std::indirect_strict_weak_order<EnsureValueCategoryComp, std::projected<const int *, MoveProj>>);
106
100
107
- auto moving_comp = [](CheckDoubleMove lhs, CheckDoubleMove rhs) { return lhs.i < rhs.i ; };
108
- auto prvalue_proj = [](const CheckDoubleMove& x) -> CheckDoubleMove { return x; };
109
- assert (&std::ranges::clamp (val, low, high, moving_comp, prvalue_proj) == &val);
101
+ assert (std::ranges::clamp (0 , 1 , 2 , EnsureValueCategoryComp{}, MoveProj{}) == 1 );
102
+ }
103
+
104
+ { // Make sure we don't call the projection more than three times per [alg.clamp], see #64717
105
+ int counter = 0 ;
106
+ auto projection_function = [&counter](const int value) -> int {
107
+ counter++;
108
+ return value;
109
+ };
110
+ assert (std::ranges::clamp (3 , 2 , 4 , std::ranges::less{}, projection_function) == 3 );
111
+ #if !(defined(_LIBCPP_ENABLE_SAFE_MODE) || defined(_LIBCPP_ENABLE_DEBUG_MODE))
112
+ assert (counter <= 3 );
113
+ #endif
110
114
}
111
115
112
116
return true ;
0 commit comments