
The implementation makes use of the freedom added by LWG 3410. We have two variants of this algorithm: * a fast path for random access iterators: This fast path computes the maximum number of loop iterations up-front and does not compare the iterators against their limits on every loop iteration. * A basic implementation for all other iterators: This implementation compares the iterators against their limits in every loop iteration. However, it still takes advantage of the freedom added by LWG 3410 to avoid unnecessary additional iterator comparisons, as originally specified by P1614R2. https://godbolt.org/z/7xbMEen5e shows the benefit of the fast path: The hot loop generated of `lexicographical_compare_three_way3` is more tight than for `lexicographical_compare_three_way1`. The added benchmark illustrates how this leads to a 30% - 50% performance improvement on integer vectors. Implements part of P1614R2 "The Mothership has Landed" Fixes LWG 3410 and LWG 3350 Differential Revision: https://reviews.llvm.org/D131395
273 lines
10 KiB
C++
273 lines
10 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
// A set of routines for testing the comparison operators of a type
|
|
//
|
|
// FooOrder<expected-ordering> All seven comparison operators, requires C++20 or newer.
|
|
// FooComparison All six pre-C++20 comparison operators
|
|
// FooEquality Equality operators operator== and operator!=
|
|
//
|
|
// AssertXAreNoexcept static_asserts that the operations are all noexcept.
|
|
// AssertXReturnBool static_asserts that the operations return bool.
|
|
// AssertOrderReturn static_asserts that the pre-C++20 comparison operations
|
|
// return bool and operator<=> returns the proper type.
|
|
// AssertXConvertibleToBool static_asserts that the operations return something convertible to bool.
|
|
// testXValues returns the result of the comparison of all operations.
|
|
//
|
|
// AssertOrderConvertibleToBool doesn't exist yet. It will be implemented when needed.
|
|
|
|
#ifndef TEST_COMPARISONS_H
|
|
#define TEST_COMPARISONS_H
|
|
|
|
#include <cassert>
|
|
#include <compare>
|
|
#include <concepts>
|
|
#include <limits>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "test_macros.h"
|
|
|
|
// Test the consistency of the six basic comparison operators for values that are ordered or unordered.
|
|
template <class T, class U = T>
|
|
TEST_NODISCARD TEST_CONSTEXPR_CXX14 bool
|
|
testComparisonsComplete(const T& t1, const U& t2, bool isEqual, bool isLess, bool isGreater) {
|
|
assert(((isEqual ? 1 : 0) + (isLess ? 1 : 0) + (isGreater ? 1 : 0) <= 1) &&
|
|
"at most one of isEqual, isLess, and isGreater can be true");
|
|
if (isEqual) {
|
|
if (!(t1 == t2)) return false;
|
|
if (!(t2 == t1)) return false;
|
|
if ( (t1 != t2)) return false;
|
|
if ( (t2 != t1)) return false;
|
|
if ( (t1 < t2)) return false;
|
|
if ( (t2 < t1)) return false;
|
|
if (!(t1 <= t2)) return false;
|
|
if (!(t2 <= t1)) return false;
|
|
if ( (t1 > t2)) return false;
|
|
if ( (t2 > t1)) return false;
|
|
if (!(t1 >= t2)) return false;
|
|
if (!(t2 >= t1)) return false;
|
|
} else if (isLess) {
|
|
if ( (t1 == t2)) return false;
|
|
if ( (t2 == t1)) return false;
|
|
if (!(t1 != t2)) return false;
|
|
if (!(t2 != t1)) return false;
|
|
if (!(t1 < t2)) return false;
|
|
if ( (t2 < t1)) return false;
|
|
if (!(t1 <= t2)) return false;
|
|
if ( (t2 <= t1)) return false;
|
|
if ( (t1 > t2)) return false;
|
|
if (!(t2 > t1)) return false;
|
|
if ( (t1 >= t2)) return false;
|
|
if (!(t2 >= t1)) return false;
|
|
} else if (isGreater) {
|
|
if ( (t1 == t2)) return false;
|
|
if ( (t2 == t1)) return false;
|
|
if (!(t1 != t2)) return false;
|
|
if (!(t2 != t1)) return false;
|
|
if ( (t1 < t2)) return false;
|
|
if (!(t2 < t1)) return false;
|
|
if ( (t1 <= t2)) return false;
|
|
if (!(t2 <= t1)) return false;
|
|
if (!(t1 > t2)) return false;
|
|
if ( (t2 > t1)) return false;
|
|
if (!(t1 >= t2)) return false;
|
|
if ( (t2 >= t1)) return false;
|
|
} else { // unordered
|
|
if ( (t1 == t2)) return false;
|
|
if ( (t2 == t1)) return false;
|
|
if (!(t1 != t2)) return false;
|
|
if (!(t2 != t1)) return false;
|
|
if ( (t1 < t2)) return false;
|
|
if ( (t2 < t1)) return false;
|
|
if ( (t1 <= t2)) return false;
|
|
if ( (t2 <= t1)) return false;
|
|
if ( (t1 > t2)) return false;
|
|
if ( (t2 > t1)) return false;
|
|
if ( (t1 >= t2)) return false;
|
|
if ( (t2 >= t1)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Test the six basic comparison operators for ordered values.
|
|
template <class T, class U = T>
|
|
TEST_NODISCARD TEST_CONSTEXPR_CXX14 bool testComparisons(const T& t1, const U& t2, bool isEqual, bool isLess) {
|
|
assert(!(isEqual && isLess) && "isEqual and isLess cannot be both true");
|
|
bool isGreater = !isEqual && !isLess;
|
|
return testComparisonsComplete(t1, t2, isEqual, isLess, isGreater);
|
|
}
|
|
|
|
// Easy call when you can init from something already comparable.
|
|
template <class T, class Param>
|
|
TEST_NODISCARD TEST_CONSTEXPR_CXX14 bool testComparisonsValues(Param val1, Param val2)
|
|
{
|
|
const bool isEqual = val1 == val2;
|
|
const bool isLess = val1 < val2;
|
|
const bool isGreater = val1 > val2;
|
|
|
|
return testComparisonsComplete(T(val1), T(val2), isEqual, isLess, isGreater);
|
|
}
|
|
|
|
template <class T, class U = T>
|
|
TEST_CONSTEXPR_CXX14 void AssertComparisonsAreNoexcept() {
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() == std::declval<const U&>());
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() != std::declval<const U&>());
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() < std::declval<const U&>());
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() <= std::declval<const U&>());
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() > std::declval<const U&>());
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() >= std::declval<const U&>());
|
|
}
|
|
|
|
template <class T, class U = T>
|
|
TEST_CONSTEXPR_CXX14 void AssertComparisonsReturnBool() {
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() == std::declval<const U&>()), bool);
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() != std::declval<const U&>()), bool);
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() < std::declval<const U&>()), bool);
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() <= std::declval<const U&>()), bool);
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() > std::declval<const U&>()), bool);
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() >= std::declval<const U&>()), bool);
|
|
}
|
|
|
|
template <class T, class U = T>
|
|
void AssertComparisonsConvertibleToBool()
|
|
{
|
|
static_assert((std::is_convertible<decltype(std::declval<const T&>() == std::declval<const U&>()), bool>::value), "");
|
|
static_assert((std::is_convertible<decltype(std::declval<const T&>() != std::declval<const U&>()), bool>::value), "");
|
|
static_assert((std::is_convertible<decltype(std::declval<const T&>() < std::declval<const U&>()), bool>::value), "");
|
|
static_assert((std::is_convertible<decltype(std::declval<const T&>() <= std::declval<const U&>()), bool>::value), "");
|
|
static_assert((std::is_convertible<decltype(std::declval<const T&>() > std::declval<const U&>()), bool>::value), "");
|
|
static_assert((std::is_convertible<decltype(std::declval<const T&>() >= std::declval<const U&>()), bool>::value), "");
|
|
}
|
|
|
|
#if TEST_STD_VER > 17
|
|
template <class T, class U = T>
|
|
constexpr void AssertOrderAreNoexcept() {
|
|
AssertComparisonsAreNoexcept<T, U>();
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() <=> std::declval<const U&>());
|
|
}
|
|
|
|
template <class Order, class T, class U = T>
|
|
constexpr void AssertOrderReturn() {
|
|
AssertComparisonsReturnBool<T, U>();
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() <=> std::declval<const U&>()), Order);
|
|
}
|
|
|
|
template <class Order, class T, class U = T>
|
|
TEST_NODISCARD constexpr bool testOrder(const T& t1, const U& t2, Order order) {
|
|
bool equal = order == Order::equivalent;
|
|
bool less = order == Order::less;
|
|
bool greater = order == Order::greater;
|
|
|
|
return (t1 <=> t2 == order) && testComparisonsComplete(t1, t2, equal, less, greater);
|
|
}
|
|
|
|
template <class T, class Param>
|
|
TEST_NODISCARD constexpr bool testOrderValues(Param val1, Param val2) {
|
|
return testOrder(T(val1), T(val2), val1 <=> val2);
|
|
}
|
|
|
|
#endif
|
|
|
|
// Test all two comparison operations for sanity
|
|
template <class T, class U = T>
|
|
TEST_NODISCARD TEST_CONSTEXPR_CXX14 bool testEquality(const T& t1, const U& t2, bool isEqual)
|
|
{
|
|
if (isEqual)
|
|
{
|
|
if (!(t1 == t2)) return false;
|
|
if (!(t2 == t1)) return false;
|
|
if ( (t1 != t2)) return false;
|
|
if ( (t2 != t1)) return false;
|
|
}
|
|
else /* not equal */
|
|
{
|
|
if ( (t1 == t2)) return false;
|
|
if ( (t2 == t1)) return false;
|
|
if (!(t1 != t2)) return false;
|
|
if (!(t2 != t1)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Easy call when you can init from something already comparable.
|
|
template <class T, class Param>
|
|
TEST_NODISCARD TEST_CONSTEXPR_CXX14 bool testEqualityValues(Param val1, Param val2)
|
|
{
|
|
const bool isEqual = val1 == val2;
|
|
|
|
return testEquality(T(val1), T(val2), isEqual);
|
|
}
|
|
|
|
template <class T, class U = T>
|
|
void AssertEqualityAreNoexcept()
|
|
{
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() == std::declval<const U&>());
|
|
ASSERT_NOEXCEPT(std::declval<const T&>() != std::declval<const U&>());
|
|
}
|
|
|
|
template <class T, class U = T>
|
|
void AssertEqualityReturnBool()
|
|
{
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() == std::declval<const U&>()), bool);
|
|
ASSERT_SAME_TYPE(decltype(std::declval<const T&>() != std::declval<const U&>()), bool);
|
|
}
|
|
|
|
|
|
template <class T, class U = T>
|
|
void AssertEqualityConvertibleToBool()
|
|
{
|
|
static_assert((std::is_convertible<decltype(std::declval<const T&>() == std::declval<const U&>()), bool>::value), "");
|
|
static_assert((std::is_convertible<decltype(std::declval<const T&>() != std::declval<const U&>()), bool>::value), "");
|
|
}
|
|
|
|
struct LessAndEqComp {
|
|
int value;
|
|
|
|
TEST_CONSTEXPR_CXX14 LessAndEqComp(int v) : value(v) {}
|
|
|
|
friend TEST_CONSTEXPR_CXX14 bool operator<(const LessAndEqComp& lhs, const LessAndEqComp& rhs) {
|
|
return lhs.value < rhs.value;
|
|
}
|
|
|
|
friend TEST_CONSTEXPR_CXX14 bool operator==(const LessAndEqComp& lhs, const LessAndEqComp& rhs) {
|
|
return lhs.value == rhs.value;
|
|
}
|
|
};
|
|
|
|
#if TEST_STD_VER > 17
|
|
struct StrongOrder {
|
|
int value;
|
|
constexpr StrongOrder(int v) : value(v) {}
|
|
friend std::strong_ordering operator<=>(StrongOrder, StrongOrder) = default;
|
|
};
|
|
|
|
struct WeakOrder {
|
|
int value;
|
|
constexpr WeakOrder(int v) : value(v) {}
|
|
friend std::weak_ordering operator<=>(WeakOrder, WeakOrder) = default;
|
|
};
|
|
|
|
struct PartialOrder {
|
|
int value;
|
|
constexpr PartialOrder(int v) : value(v) {}
|
|
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;
|
|
return lhs.value <=> rhs.value;
|
|
}
|
|
friend constexpr bool operator==(PartialOrder lhs, PartialOrder rhs) {
|
|
return (lhs <=> rhs) == std::partial_ordering::equivalent;
|
|
}
|
|
};
|
|
|
|
#endif
|
|
|
|
#endif // TEST_COMPARISONS_H
|