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