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 // UNSUPPORTED: c++03, c++11, c++14, c++17
10
11 // constexpr auto synth-three-way = ...;
12 // via std::tuple<T>(t) <=> std::tuple<U>(u), which exposes its behavior most directly
13
14 #include "test_macros.h"
15
16 TEST_CLANG_DIAGNOSTIC_IGNORED("-Wsign-compare")
17 TEST_GCC_DIAGNOSTIC_IGNORED("-Wsign-compare")
18 TEST_MSVC_DIAGNOSTIC_IGNORED(4242 4244) // Various truncation warnings
19
20 #include <cassert>
21 #include <compare>
22 #include <limits> // quiet_NaN
23 #include <tuple>
24 #include <type_traits>
25 #include <utility> // declval
26
27 template <typename T, typename U = T>
28 concept can_synth_three_way = requires(T t, U u) { std::tuple<T>(t) <=> std::tuple<U>(u); };
29
30 template <typename T, typename U>
synth_three_way(const T & t,const U & u)31 constexpr auto synth_three_way(const T& t, const U& u) {
32 return std::tuple<T>(t) <=> std::tuple<U>(u);
33 }
34
35 template <typename T, typename U>
36 using synth_three_way_result = decltype(std::declval<std::tuple<T>>() <=> std::declval<std::tuple<U>>());
37
38 // A custom three-way result type
39 struct CustomEquality {
operator ==(const CustomEquality &,int)40 friend constexpr bool operator==(const CustomEquality&, int) noexcept { return true; }
operator <(const CustomEquality &,int)41 friend constexpr bool operator<(const CustomEquality&, int) noexcept { return false; }
operator <(int,const CustomEquality &)42 friend constexpr bool operator<(int, const CustomEquality&) noexcept { return false; }
43 };
44
test()45 constexpr bool test() {
46 {
47 assert(synth_three_way(1, 1) == std::strong_ordering::equal);
48 assert(synth_three_way(2, 1) == std::strong_ordering::greater);
49 assert(synth_three_way(1, 2) == std::strong_ordering::less);
50 ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<int, int>);
51 ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<short, long long int>);
52 }
53 {
54 constexpr double nan = std::numeric_limits<double>::quiet_NaN();
55 assert(synth_three_way(1.0, 1.0) == std::partial_ordering::equivalent);
56 assert(synth_three_way(2.0, 1.0) == std::partial_ordering::greater);
57 assert(synth_three_way(1.0, 2.0) == std::partial_ordering::less);
58 assert(synth_three_way(nan, nan) == std::partial_ordering::unordered);
59 ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, double>);
60 ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, float>);
61 ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, int>);
62 ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<float, short>);
63 }
64 {
65 struct StrongSpaceship {
66 int value;
67 constexpr bool operator==(const StrongSpaceship&) const = default;
68 constexpr std::strong_ordering operator<=>(const StrongSpaceship& other) const { return value <=> other.value; }
69 };
70 assert(synth_three_way(StrongSpaceship{1}, StrongSpaceship{1}) == std::strong_ordering::equal);
71 assert(synth_three_way(StrongSpaceship{2}, StrongSpaceship{1}) == std::strong_ordering::greater);
72 assert(synth_three_way(StrongSpaceship{1}, StrongSpaceship{2}) == std::strong_ordering::less);
73 ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<StrongSpaceship, StrongSpaceship>);
74 }
75 {
76 struct WeakSpaceship {
77 int value;
78 constexpr bool operator==(const WeakSpaceship&) const = default;
79 constexpr std::weak_ordering operator<=>(const WeakSpaceship& other) const {
80 return value <=> other.value;
81 }
82 };
83 assert(synth_three_way(WeakSpaceship{1}, WeakSpaceship{1}) == std::weak_ordering::equivalent);
84 assert(synth_three_way(WeakSpaceship{2}, WeakSpaceship{1}) == std::weak_ordering::greater);
85 assert(synth_three_way(WeakSpaceship{1}, WeakSpaceship{2}) == std::weak_ordering::less);
86 ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<WeakSpaceship, WeakSpaceship>);
87 }
88 {
89 struct PartialSpaceship {
90 double value;
91 constexpr bool operator==(const PartialSpaceship&) const = default;
92 constexpr std::partial_ordering operator<=>(const PartialSpaceship& other) const {
93 return value <=> other.value;
94 }
95 };
96 constexpr double nan = std::numeric_limits<double>::quiet_NaN();
97 assert(synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{1.0}) == std::partial_ordering::equivalent);
98 assert(synth_three_way(PartialSpaceship{2.0}, PartialSpaceship{1.0}) == std::partial_ordering::greater);
99 assert(synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{2.0}) == std::partial_ordering::less);
100 assert(synth_three_way(PartialSpaceship{nan}, PartialSpaceship{nan}) == std::partial_ordering::unordered);
101 ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<PartialSpaceship, PartialSpaceship>);
102 }
103 {
104 struct NoSpaceship {
105 int value;
106 constexpr bool operator==(const NoSpaceship&) const = default;
107 constexpr bool operator<(const NoSpaceship& other) const { return value < other.value; }
108 };
109 assert(synth_three_way(NoSpaceship{1}, NoSpaceship{1}) == std::weak_ordering::equivalent);
110 assert(synth_three_way(NoSpaceship{2}, NoSpaceship{1}) == std::weak_ordering::greater);
111 assert(synth_three_way(NoSpaceship{1}, NoSpaceship{2}) == std::weak_ordering::less);
112 ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<NoSpaceship, NoSpaceship>);
113 }
114 {
115 // Types with operator<=> but no operator== are not three_way_comparable and will fall back to operator< and
116 // compare as weakly ordered.
117 struct SpaceshipNoEquals {
118 constexpr std::strong_ordering operator<=>(const SpaceshipNoEquals&) const {
119 return std::strong_ordering::equivalent;
120 }
121 };
122 assert(synth_three_way(SpaceshipNoEquals{}, SpaceshipNoEquals{}) == std::weak_ordering::equivalent);
123 ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<SpaceshipNoEquals, SpaceshipNoEquals>);
124 }
125 {
126 // Custom three-way-comparison result types cannot satisfy standard concepts (and therefore synth-three-way)
127 // because they are not understood by std::common_comparison_category, but they can still be used in
128 // the same way as standard orderings to do comparisons, and thus can be used by synth-three-way to yield a
129 // weakly-ordered result.
130 struct CustomSpaceship {
131 constexpr CustomEquality operator<=>(const CustomSpaceship&) const { return CustomEquality(); }
132 };
133 assert((CustomSpaceship{} <=> CustomSpaceship{}) == 0);
134 assert(!(CustomSpaceship{} < CustomSpaceship{}));
135 assert(synth_three_way(CustomSpaceship{}, CustomSpaceship{}) == std::weak_ordering::equivalent);
136 ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<CustomSpaceship, CustomSpaceship>);
137 }
138 // SFINAE tests demonstrating synth-three-way needs three_way_comparable or operator<.
139 {
140 struct NoRelative {
141 constexpr bool operator==(const NoRelative&) const;
142 };
143 static_assert(!can_synth_three_way<NoRelative>);
144 }
145 {
146 struct NoLessThan {
147 constexpr bool operator==(const NoLessThan&) const;
148 constexpr bool operator>(const NoLessThan&) const;
149 constexpr bool operator>=(const NoLessThan&) const;
150 constexpr bool operator<=(const NoLessThan&) const;
151 };
152 static_assert(!can_synth_three_way<NoLessThan>);
153 }
154 {
155 assert(synth_three_way(1, 1U) == std::weak_ordering::equivalent);
156 assert(synth_three_way(-1, 0U) == std::weak_ordering::greater);
157 // Even with the warning suppressed (-Wno-sign-compare) there should still be no <=> operator
158 // between signed and unsigned types, so we should end up with a synthesized weak ordering.
159 ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<int, unsigned int>);
160 // When an unsigned type can be narrowed to a larger signed type, <=> should be defined and we
161 // should get a strong ordering. (This probably does not raise a warning due to safe narrowing.)
162 assert(synth_three_way(static_cast<long long int>(-1), static_cast<unsigned char>(0)) == std::strong_ordering::less);
163 assert(synth_three_way(static_cast<long long int>(-1), static_cast<unsigned char>(0)) == std::strong_ordering::less);
164 ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<long long int, unsigned char>);
165 }
166 #ifdef TEST_COMPILER_GCC
167 // GCC cannot evaluate NaN @ non-NaN constexpr, so test that runtime-only.
168 if (!std::is_constant_evaluated())
169 #endif
170 {
171 constexpr double nan = std::numeric_limits<double>::quiet_NaN();
172 assert(synth_three_way(nan, 1.0) == std::partial_ordering::unordered);
173 }
174
175 return true;
176 }
177
main(int,char **)178 int main(int, char**) {
179 test();
180 static_assert(test());
181
182 return 0;
183 }
184