[libc++][spaceship] Implement `operator<=>` for `multiset` and `set`
authorHristo Hristov <zingam@outlook.com>
Mon, 22 May 2023 20:33:45 +0000 (23:33 +0300)
committerHristo Hristov <zingam@outlook.com>
Mon, 29 May 2023 06:00:14 +0000 (09:00 +0300)
Implements parts of P1614R2

Implemented `operator<=>` for `multiset` and `set`

Reviewed By: #libc, Mordante

Differential Revision: https://reviews.llvm.org/D148416

libcxx/include/set
libcxx/test/std/containers/associative/multiset/multiset.nonmember/compare.three_way.pass.cpp [new file with mode: 0644]
libcxx/test/std/containers/associative/multiset/multiset.nonmember/compare.three_way.verify.cpp [new file with mode: 0644]
libcxx/test/std/containers/associative/set/set.nonmember/compare.three_way.pass.cpp [new file with mode: 0644]
libcxx/test/std/containers/associative/set/set.nonmember/compare.three_way.verify.cpp [new file with mode: 0644]
libcxx/test/support/test_comparisons.h
libcxx/test/support/test_container_comparisons.h

index b0e5e50..7d54c90 100644 (file)
@@ -210,27 +210,31 @@ operator==(const set<Key, Compare, Allocator>& x,
 template <class Key, class Compare, class Allocator>
 bool
 operator< (const set<Key, Compare, Allocator>& x,
-           const set<Key, Compare, Allocator>& y);
+           const set<Key, Compare, Allocator>& y);                                // removed in C++20
 
 template <class Key, class Compare, class Allocator>
 bool
 operator!=(const set<Key, Compare, Allocator>& x,
-           const set<Key, Compare, Allocator>& y);
+           const set<Key, Compare, Allocator>& y);                                // removed in C++20
 
 template <class Key, class Compare, class Allocator>
 bool
 operator> (const set<Key, Compare, Allocator>& x,
-           const set<Key, Compare, Allocator>& y);
+           const set<Key, Compare, Allocator>& y);                                // removed in C++20
 
 template <class Key, class Compare, class Allocator>
 bool
 operator>=(const set<Key, Compare, Allocator>& x,
-           const set<Key, Compare, Allocator>& y);
+           const set<Key, Compare, Allocator>& y);                                // removed in C++20
 
 template <class Key, class Compare, class Allocator>
 bool
 operator<=(const set<Key, Compare, Allocator>& x,
-           const set<Key, Compare, Allocator>& y);
+           const set<Key, Compare, Allocator>& y);                                // removed in C++20
+
+template<class Key, class Compare, class Allocator>
+  synth-three-way-result<Key> operator<=>(const set<Key, Compare, Allocator>& x,
+                                          const set<Key, Compare, Allocator>& y); // since C++20
 
 // specialized algorithms:
 template <class Key, class Compare, class Allocator>
@@ -435,27 +439,31 @@ operator==(const multiset<Key, Compare, Allocator>& x,
 template <class Key, class Compare, class Allocator>
 bool
 operator< (const multiset<Key, Compare, Allocator>& x,
-           const multiset<Key, Compare, Allocator>& y);
+           const multiset<Key, Compare, Allocator>& y);                                // removed in C++20
 
 template <class Key, class Compare, class Allocator>
 bool
 operator!=(const multiset<Key, Compare, Allocator>& x,
-           const multiset<Key, Compare, Allocator>& y);
+           const multiset<Key, Compare, Allocator>& y);                                // removed in C++20
 
 template <class Key, class Compare, class Allocator>
 bool
 operator> (const multiset<Key, Compare, Allocator>& x,
-           const multiset<Key, Compare, Allocator>& y);
+           const multiset<Key, Compare, Allocator>& y);                                // removed in C++20
 
 template <class Key, class Compare, class Allocator>
 bool
 operator>=(const multiset<Key, Compare, Allocator>& x,
-           const multiset<Key, Compare, Allocator>& y);
+           const multiset<Key, Compare, Allocator>& y);                                // removed in C++20
 
 template <class Key, class Compare, class Allocator>
 bool
 operator<=(const multiset<Key, Compare, Allocator>& x,
-           const multiset<Key, Compare, Allocator>& y);
+           const multiset<Key, Compare, Allocator>& y);                                // removed in C++20
+
+template<class Key, class Compare, class Allocator>
+  synth-three-way-result<Key> operator<=>(const multiset<Key, Compare, Allocator>& x,
+                                          const multiset<Key, Compare, Allocator>& y); // since C++20
 
 // specialized algorithms:
 template <class Key, class Compare, class Allocator>
@@ -473,6 +481,7 @@ erase_if(multiset<Key, Compare, Allocator>& c, Predicate pred);  // C++20
 
 #include <__algorithm/equal.h>
 #include <__algorithm/lexicographical_compare.h>
+#include <__algorithm/lexicographical_compare_three_way.h>
 #include <__assert> // all public C++ headers provide the assertion handler
 #include <__config>
 #include <__functional/is_transparent.h>
@@ -982,6 +991,8 @@ operator==(const set<_Key, _Compare, _Allocator>& __x,
     return __x.size() == __y.size() && _VSTD::equal(__x.begin(), __x.end(), __y.begin());
 }
 
+#if _LIBCPP_STD_VER <= 17
+
 template <class _Key, class _Compare, class _Allocator>
 inline _LIBCPP_INLINE_VISIBILITY
 bool
@@ -1027,6 +1038,17 @@ operator<=(const set<_Key, _Compare, _Allocator>& __x,
     return !(__y < __x);
 }
 
+#else // _LIBCPP_STD_VER <= 17
+
+template <class _Key, class _Allocator>
+_LIBCPP_HIDE_FROM_ABI __synth_three_way_result<_Key>
+operator<=>(const set<_Key, _Allocator>& __x, const set<_Key, _Allocator>& __y) {
+    return std::lexicographical_compare_three_way(
+        __x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way<_Key, _Key>);
+}
+
+#endif // _LIBCPP_STD_VER <= 17
+
 // specialized algorithms:
 template <class _Key, class _Compare, class _Allocator>
 inline _LIBCPP_INLINE_VISIBILITY
@@ -1518,6 +1540,8 @@ operator==(const multiset<_Key, _Compare, _Allocator>& __x,
     return __x.size() == __y.size() && _VSTD::equal(__x.begin(), __x.end(), __y.begin());
 }
 
+#if _LIBCPP_STD_VER <= 17
+
 template <class _Key, class _Compare, class _Allocator>
 inline _LIBCPP_INLINE_VISIBILITY
 bool
@@ -1563,6 +1587,17 @@ operator<=(const multiset<_Key, _Compare, _Allocator>& __x,
     return !(__y < __x);
 }
 
+#else // _LIBCPP_STD_VER <= 17
+
+template <class _Key, class _Allocator>
+_LIBCPP_HIDE_FROM_ABI __synth_three_way_result<_Key>
+operator<=>(const multiset<_Key, _Allocator>& __x, const multiset<_Key, _Allocator>& __y) {
+    return std::lexicographical_compare_three_way(
+        __x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way<_Key, _Key>);
+}
+
+#endif // _LIBCPP_STD_VER <= 17
+
 template <class _Key, class _Compare, class _Allocator>
 inline _LIBCPP_INLINE_VISIBILITY
 void
diff --git a/libcxx/test/std/containers/associative/multiset/multiset.nonmember/compare.three_way.pass.cpp b/libcxx/test/std/containers/associative/multiset/multiset.nonmember/compare.three_way.pass.cpp
new file mode 100644 (file)
index 0000000..fb575c8
--- /dev/null
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <set>
+
+// class multiset
+
+// template<class Key, class Compare, class Allocator>
+//   synth-three-way-result<Key> operator<=>(const multiset<Key, Compare, Allocator>& x,
+//                                           const multiset<Key, Compare, Allocator>& y);
+
+#include <cassert>
+#include <set>
+
+#include "test_container_comparisons.h"
+
+int main(int, char**) {
+  assert(test_ordered_set_container_spaceship<std::multiset>());
+  // `std::multiset` is not constexpr, so no `static_assert` test here.
+  return 0;
+}
diff --git a/libcxx/test/std/containers/associative/multiset/multiset.nonmember/compare.three_way.verify.cpp b/libcxx/test/std/containers/associative/multiset/multiset.nonmember/compare.three_way.verify.cpp
new file mode 100644 (file)
index 0000000..9bde360
--- /dev/null
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <set>
+
+// class set
+
+// template<class Key, class Compare, class Allocator>
+//   synth-three-way-result<Key> operator<=>(const set<Key, Compare, Allocator>& x,
+//                                           const set<Key, Compare, Allocator>& y);
+
+#include <set>
+
+#include "test_allocator.h"
+
+int main(int, char**) {
+  // Mismatching allocators
+  {
+    std::multiset<int, std::less<int>, std::allocator<int>> s1;
+    std::multiset<int, std::less<int>, test_allocator<int>> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s1 <=> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s2 <=> s1;
+  }
+  // Mismatching comparision functions
+  {
+    std::multiset<int, std::less<int>> s1;
+    std::multiset<int, std::greater<int>> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s1 <=> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s2 <=> s1;
+  }
+  {
+    std::multiset<int, std::less<int>> s1;
+    std::multiset<int, std::less<float>> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s1 <=> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s2 <=> s1;
+  }
+  // Mismatching types
+  {
+    std::multiset<int> s1;
+    std::multiset<float> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s1 <=> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s2 <=> s1;
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/std/containers/associative/set/set.nonmember/compare.three_way.pass.cpp b/libcxx/test/std/containers/associative/set/set.nonmember/compare.three_way.pass.cpp
new file mode 100644 (file)
index 0000000..dc513d9
--- /dev/null
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <set>
+
+// class set
+
+// template<class Key, class Compare, class Allocator>
+//   synth-three-way-result<Key> operator<=>(const set<Key, Compare, Allocator>& x,
+//                                           const set<Key, Compare, Allocator>& y);
+
+#include <cassert>
+#include <set>
+
+#include "test_container_comparisons.h"
+
+int main(int, char**) {
+  assert((test_ordered_set_container_spaceship<std::set>()));
+  // `std::set` is not constexpr, so no `static_assert` test here.
+  return 0;
+}
diff --git a/libcxx/test/std/containers/associative/set/set.nonmember/compare.three_way.verify.cpp b/libcxx/test/std/containers/associative/set/set.nonmember/compare.three_way.verify.cpp
new file mode 100644 (file)
index 0000000..a2d8c4e
--- /dev/null
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <set>
+
+// class set
+
+// template<class Key, class Compare, class Allocator>
+//   synth-three-way-result<Key> operator<=>(const set<Key, Compare, Allocator>& x,
+//                                           const set<Key, Compare, Allocator>& y);
+
+#include <set>
+
+#include "test_allocator.h"
+
+int main(int, char**) {
+  // Mismatching allocators
+  {
+    std::set<int, std::less<int>, std::allocator<int>> s1;
+    std::set<int, std::less<int>, test_allocator<int>> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s1 <=> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s2 <=> s1;
+  }
+  // Mismatching comparision functions
+  {
+    std::set<int, std::less<int>> s1;
+    std::set<int, std::greater<int>> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s1 <=> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s2 <=> s1;
+  }
+  {
+    std::set<int, std::less<int>> s1;
+    std::set<int, std::less<float>> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s1 <=> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s2 <=> s1;
+  }
+  // Mismatching types
+  {
+    std::set<int> s1;
+    std::set<float> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s1 <=> s2;
+    // expected-error@+1 {{invalid operands to binary expression}}
+    s2 <=> s1;
+  }
+
+  return 0;
+}
index c54e877..e006f69 100644 (file)
@@ -241,7 +241,8 @@ struct LessAndEqComp {
   }
 };
 
-#if TEST_STD_VER > 17
+#if TEST_STD_VER >= 20
+
 struct StrongOrder {
   int value;
   constexpr StrongOrder(int v) : value(v) {}
@@ -260,6 +261,8 @@ struct PartialOrder {
   friend constexpr std::partial_ordering operator<=>(PartialOrder lhs, PartialOrder rhs) {
     if (lhs.value == std::numeric_limits<int>::min() || rhs.value == std::numeric_limits<int>::min())
       return std::partial_ordering::unordered;
+    if (lhs.value == std::numeric_limits<int>::max() || rhs.value == std::numeric_limits<int>::max())
+      return std::partial_ordering::unordered;
     return lhs.value <=> rhs.value;
   }
   friend constexpr bool operator==(PartialOrder lhs, PartialOrder rhs) {
index f9dae9d..d3b4033 100644 (file)
@@ -10,6 +10,8 @@
 #ifndef TEST_CONTAINER_COMPARISONS
 #define TEST_CONTAINER_COMPARISONS
 
+#include <set>
+
 #include "test_comparisons.h"
 
 // Implementation detail of `test_sequence_container_spaceship`
@@ -183,7 +185,7 @@ constexpr void test_ordered_map_container_spaceship_with_type() {
   }
 }
 
-// Tests the `operator<=>` on ordered containers
+// Tests the `operator<=>` on ordered map containers
 template <template <typename...> typename Container>
 constexpr bool test_ordered_map_container_spaceship() {
   // The container should fulfill `std::three_way_comparable`
@@ -205,4 +207,107 @@ constexpr bool test_ordered_map_container_spaceship() {
   return true;
 }
 
+// Implementation detail of `test_ordered_set_container_spaceship`
+template <template <typename...> typename Container, typename Elem, typename Order, typename Compare>
+constexpr void test_ordered_set_spaceship_with_type(Compare comp) {
+  // Empty containers
+  {
+    Container<Elem, Compare> l1{{}, comp};
+    Container<Elem, Compare> l2{{}, comp};
+    assert(testOrder(l1, l2, Order::equivalent));
+  }
+  // Identical contents
+  {
+    Container<Elem, Compare> l1{{1, 1, 2}, comp};
+    Container<Elem, Compare> l2{{1, 1, 2}, comp};
+    assert(testOrder(l1, l2, Order::equivalent));
+  }
+  // Less, due to contained values
+  {
+    Container<Elem, Compare> l1{{1, 1, 2, 3}, comp};
+    Container<Elem, Compare> l2{{1, 2, 2, 4}, comp};
+    assert(testOrder(l1, l2, Order::less));
+  }
+  // Greater, due to contained values
+  {
+    Container<Elem, Compare> l1{{1, 2, 2, 4}, comp};
+    Container<Elem, Compare> l2{{1, 1, 2, 3}, comp};
+    assert(testOrder(l1, l2, Order::greater));
+  }
+  // Shorter list
+  {
+    Container<Elem, Compare> l1{{1, 1, 2, 2}, comp};
+    Container<Elem, Compare> l2{{1, 1, 2, 2, 3}, comp};
+    assert(testOrder(l1, l2, Order::less));
+  }
+  // Longer list
+  {
+    Container<Elem, Compare> l1{{1, 1, 2, 2, 3}, comp};
+    Container<Elem, Compare> l2{{1, 1, 2, 2}, comp};
+    assert(testOrder(l1, l2, Order::greater));
+  }
+  // Unordered
+  if constexpr (std::is_same_v< Container<Elem>, std::multiset<PartialOrder>>) {
+    if constexpr (std::is_same_v<Elem, PartialOrder> && std::is_same_v<Compare, decltype(std::less{})>) {
+      Container<Elem, Compare> l1{{1, std::numeric_limits<int>::min()}, comp};
+      Container<Elem, Compare> l2{{1, 2}, comp};
+      assert(testOrder(l1, l2, Order::unordered));
+    }
+    if constexpr (std::is_same_v<Elem, PartialOrder> && std::is_same_v<Compare, decltype(std::less{})>) {
+      Container<Elem, Compare> l1{{1, std::numeric_limits<int>::max()}, comp};
+      Container<Elem, Compare> l2{{1, 2}, comp};
+      assert(testOrder(l1, l2, Order::unordered));
+    }
+  }
+  if constexpr (std::is_same_v< Container<Elem>, std::set<PartialOrder>>) {
+    // Unodered values are not supported for `set`
+    if constexpr (std::is_same_v<Elem, PartialOrder> && std::is_same_v<Compare, decltype(std::less{})>) {
+      Container<Elem, Compare> l1{{1, std::numeric_limits<int>::min()}, comp};
+      Container<Elem, Compare> l2{{1, 2}, comp};
+      assert(testOrder(l1, l2, Order::less));
+    }
+    if constexpr (std::is_same_v<Elem, PartialOrder> && std::is_same_v<Compare, decltype(std::less{})>) {
+      Container<Elem, Compare> l1{{1, std::numeric_limits<int>::max()}, comp};
+      Container<Elem, Compare> l2{{1, 2}, comp};
+      assert(testOrder(l1, l2, Order::less));
+    }
+  }
+  if constexpr (std::is_same_v<Elem, PartialOrder> && std::is_same_v<Compare, decltype(std::greater{})>) {
+    Container<Elem, Compare> l1{{1, std::numeric_limits<int>::min()}, comp};
+    Container<Elem, Compare> l2{{1, 2}, comp};
+    assert(testOrder(l1, l2, Order::less));
+  }
+  if constexpr (std::is_same_v<Elem, PartialOrder> && std::is_same_v<Compare, decltype(std::greater{})>) {
+    Container<Elem, Compare> l1{{1, std::numeric_limits<int>::max()}, comp};
+    Container<Elem, Compare> l2{{1, 2}, comp};
+    assert(testOrder(l1, l2, Order::less));
+  }
+}
+
+// Tests the `operator<=>` on ordered set containers
+template <template <typename...> typename Container>
+constexpr bool test_ordered_set_container_spaceship() {
+  // Thanks to SFINAE, the following is not a compiler error but returns `false`
+  struct NonComparable {};
+  static_assert(!std::three_way_comparable<Container<NonComparable>>);
+
+  // The container should fulfill `std::three_way_comparable`
+  static_assert(std::three_way_comparable<Container<int>>);
+
+  // Test different comparison categories
+  test_ordered_set_spaceship_with_type<Container, int, std::strong_ordering>(std::less{});
+  test_ordered_set_spaceship_with_type<Container, int, std::strong_ordering>(std::greater{});
+  test_ordered_set_spaceship_with_type<Container, StrongOrder, std::strong_ordering>(std::less{});
+  test_ordered_set_spaceship_with_type<Container, StrongOrder, std::strong_ordering>(std::greater{});
+  test_ordered_set_spaceship_with_type<Container, WeakOrder, std::weak_ordering>(std::less{});
+  test_ordered_set_spaceship_with_type<Container, WeakOrder, std::weak_ordering>(std::greater{});
+  test_ordered_set_spaceship_with_type<Container, PartialOrder, std::partial_ordering>(std::less{});
+  test_ordered_set_spaceship_with_type<Container, PartialOrder, std::partial_ordering>(std::greater{});
+
+  // `LessAndEqComp` does not have `operator<=>`. Ordering is synthesized based on `operator<`
+  test_ordered_set_spaceship_with_type<Container, LessAndEqComp, std::weak_ordering>(std::less{});
+
+  return true;
+}
+
 #endif