[libc++][spaceship] Implement operator<=> for multiset and set

Implements parts of P1614R2

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

Reviewed By: #libc, Mordante

Differential Revision: https://reviews.llvm.org/D148416
This commit is contained in:
Hristo Hristov 2023-05-22 23:33:45 +03:00
parent dd0cf23e4a
commit f8b5ac34ad
7 changed files with 329 additions and 12 deletions

View 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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View 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) {

View 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