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