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