From c2aecef3dd6cd83ddd66d2aaf845e888610369af Mon Sep 17 00:00:00 2001 From: _Redstone_c_ Date: Wed, 9 Mar 2022 23:17:54 +0800 Subject: [PATCH] feat(templates): add three-way comparison operator support --- .../Private/Testing/ConceptsTesting.cpp | 1 + .../Private/Testing/TemplatesTesting.cpp | 129 ++++++++++ .../Source/Public/Templates/Compare.h | 102 ++++++++ .../Source/Public/Templates/Optional.h | 223 ++++++++++++++++++ .../Source/Public/Templates/Templates.h | 1 + .../Source/Public/Testing/TemplatesTesting.h | 1 + 6 files changed, 457 insertions(+) create mode 100644 Redcraft.Utility/Source/Public/Templates/Compare.h create mode 100644 Redcraft.Utility/Source/Public/Templates/Optional.h diff --git a/Redcraft.Utility/Source/Private/Testing/ConceptsTesting.cpp b/Redcraft.Utility/Source/Private/Testing/ConceptsTesting.cpp index e0f86a7..6134180 100644 --- a/Redcraft.Utility/Source/Private/Testing/ConceptsTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/ConceptsTesting.cpp @@ -1,5 +1,6 @@ #include "Testing/ConceptsTesting.h" #include "Miscellaneous/AssertionMacros.h" +#include "Templates/Templates.h" #include "Concepts/Concepts.h" NAMESPACE_REDCRAFT_BEGIN diff --git a/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp b/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp index afcab32..4823826 100644 --- a/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp @@ -10,6 +10,7 @@ void TestTemplates() { TestInvoke(); TestReferenceWrapper(); + TestCompare(); TestMiscellaneous(); } @@ -68,6 +69,134 @@ void TestReferenceWrapper() NAMESPACE_UNNAMED_BEGIN +struct FTestPartialOrdering +{ + int32 Num; + bool bIsValid; + FTestPartialOrdering(int32 InNum, bool bInIsValid = true) : Num(InNum), bIsValid(bInIsValid) { } + friend bool operator==(FTestPartialOrdering LHS, FTestPartialOrdering RHS) { return LHS.bIsValid && RHS.bIsValid ? LHS.Num == RHS.Num : false; } + friend partial_ordering operator<=>(FTestPartialOrdering LHS, FTestPartialOrdering RHS) { return LHS.bIsValid && RHS.bIsValid ? LHS.Num <=> RHS.Num : partial_ordering::unordered; } +}; + +struct FTestWeakOrdering +{ + int32 Num; + FTestWeakOrdering(int32 InNum) : Num(InNum) { } + friend bool operator==(FTestWeakOrdering LHS, FTestWeakOrdering RHS) { return LHS.Num == RHS.Num; } + friend weak_ordering operator<=>(FTestWeakOrdering LHS, FTestWeakOrdering RHS) { return LHS.Num <=> RHS.Num; } +}; + +struct FTestStrongOrdering +{ + int32 Num; + FTestStrongOrdering(int32 InNum) : Num(InNum) { } + friend bool operator==(FTestStrongOrdering LHS, FTestStrongOrdering RHS) { return LHS.Num == RHS.Num; } + friend strong_ordering operator<=>(FTestStrongOrdering LHS, FTestStrongOrdering RHS) { return LHS.Num <=> RHS.Num; } +}; + +NAMESPACE_UNNAMED_END + +void TestCompare() +{ + always_check((-1 <=> 0) == strong_ordering::less); + always_check(( 0 <=> 0) == strong_ordering::equivalent); + always_check(( 0 <=> 0) == strong_ordering::equal); + always_check(( 0 <=> -1) == strong_ordering::greater); + + always_check((-1 <=> 0) < 0); + always_check((-1 <=> 0) <= 0); + always_check(( 0 <=> 0) <= 0); + always_check(( 0 <=> 0) == 0); + always_check(( 0 <=> 0) >= 0); + always_check(( 0 <=> -1) >= 0); + always_check(( 0 <=> -1) > 0); + always_check((-1 <=> 1) != 0); + + int64 NaNBit = 0xFFF8000000000000; + double NaN = *reinterpret_cast(&NaNBit); + + always_check((-1.0 <=> 0.0) == partial_ordering::less); + always_check(( 0.0 <=> 0.0) == partial_ordering::equivalent); + always_check(( 0.0 <=> -1.0) == partial_ordering::greater); + always_check(( 0.0 <=> NaN) == partial_ordering::unordered); + + always_check((-1.0 <=> 0.0) == weak_ordering::less); + always_check(( 0.0 <=> 0.0) == weak_ordering::equivalent); + always_check(( 0.0 <=> -1.0) == weak_ordering::greater); + + always_check((-1.0 <=> 0.0) == strong_ordering::less); + always_check(( 0.0 <=> 0.0) == strong_ordering::equivalent); + always_check(( 0.0 <=> 0.0) == strong_ordering::equal); + always_check(( 0.0 <=> -1.0) == strong_ordering::greater); + + always_check((-1.0 <=> 0.0) < 0); + always_check((-1.0 <=> 0.0) <= 0); + always_check(( 0.0 <=> 0.0) <= 0); + always_check(( 0.0 <=> 0.0) == 0); + always_check(( 0.0 <=> 0.0) >= 0); + always_check(( 0.0 <=> -1.0) >= 0); + always_check(( 0.0 <=> -1.0) > 0); + always_check((-1.0 <=> 1.0) != 0); + + always_check((FTestPartialOrdering(-1) <=> FTestPartialOrdering( 0)) == partial_ordering::less); + always_check((FTestPartialOrdering( 0) <=> FTestPartialOrdering( 0)) == partial_ordering::equivalent); + always_check((FTestPartialOrdering( 0) <=> FTestPartialOrdering(-1)) == partial_ordering::greater); + + always_check((FTestPartialOrdering( 0, true) <=> FTestPartialOrdering( 0, false)) == partial_ordering::unordered); + + always_check((FTestWeakOrdering(-1) <=> FTestWeakOrdering( 0)) == weak_ordering::less); + always_check((FTestWeakOrdering( 0) <=> FTestWeakOrdering( 0)) == weak_ordering::equivalent); + always_check((FTestWeakOrdering( 0) <=> FTestWeakOrdering(-1)) == weak_ordering::greater); + + always_check((FTestStrongOrdering(-1) <=> FTestStrongOrdering( 0)) == strong_ordering::less); + always_check((FTestStrongOrdering( 0) <=> FTestStrongOrdering( 0)) == strong_ordering::equivalent); + always_check((FTestStrongOrdering( 0) <=> FTestStrongOrdering( 0)) == strong_ordering::equal); + always_check((FTestStrongOrdering( 0) <=> FTestStrongOrdering(-1)) == strong_ordering::greater); + + always_check((FTestPartialOrdering(-1) < FTestPartialOrdering( 0))); + always_check((FTestPartialOrdering( 0) == FTestPartialOrdering( 0))); + always_check((FTestPartialOrdering( 0) > FTestPartialOrdering(-1))); + + always_check((FTestWeakOrdering(-1) < FTestWeakOrdering( 0))); + always_check((FTestWeakOrdering( 0) == FTestWeakOrdering( 0))); + always_check((FTestWeakOrdering( 0) > FTestWeakOrdering(-1))); + + always_check((FTestStrongOrdering(-1) < FTestStrongOrdering( 0))); + always_check((FTestStrongOrdering( 0) == FTestStrongOrdering( 0))); + always_check((FTestStrongOrdering( 0) > FTestStrongOrdering(-1))); + + always_check((TIsSame::Type, strong_ordering >::Value)); + always_check((TIsSame::Type, weak_ordering >::Value)); + always_check((TIsSame::Type, partial_ordering>::Value)); + + always_check(CThreeWayComparable); + always_check(CThreeWayComparable); + always_check(CThreeWayComparable); + always_check(CThreeWayComparable); + + always_check((CThreeWayComparableWith)); + always_check((CThreeWayComparableWith)); + + always_check((TIsSame::Type, strong_ordering >::Value)); + always_check((TIsSame::Type, partial_ordering>::Value)); + always_check((TIsSame::Type, partial_ordering>::Value)); + always_check((TIsSame::Type, weak_ordering >::Value)); + always_check((TIsSame::Type, strong_ordering >::Value)); + + always_check((TCompareThreeWay()(0, 0) == strong_ordering::equal)); + always_check((TCompareThreeWay() (0, 0.0) == strong_ordering::equal)); + + always_check((StrongOrder(0, 0) == strong_ordering::equal)); + always_check((WeakOrder(0, 0) == strong_ordering::equal)); + always_check((PartialOrder(0, 0) == strong_ordering::equal)); + always_check((CompareStrongOrderFallback(0, 0) == strong_ordering::equal)); + always_check((CompareWeakOrderFallback(0, 0) == strong_ordering::equal)); + always_check((ComparePartialOrderFallback(0, 0) == strong_ordering::equal)); + +} + +NAMESPACE_UNNAMED_BEGIN + template struct TTestStructA { diff --git a/Redcraft.Utility/Source/Public/Templates/Compare.h b/Redcraft.Utility/Source/Public/Templates/Compare.h new file mode 100644 index 0000000..f969b68 --- /dev/null +++ b/Redcraft.Utility/Source/Public/Templates/Compare.h @@ -0,0 +1,102 @@ +#pragma once + +#include "CoreTypes.h" +#include "Concepts/Concepts.h" +#include "TypeTraits/TypeTraits.h" + +#include + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +// The result of the three-way comparison operator is the built-in type of the compiler, which is directly introduced here. + +typedef NAMESPACE_STD::partial_ordering partial_ordering; +typedef NAMESPACE_STD::weak_ordering weak_ordering; +typedef NAMESPACE_STD::strong_ordering strong_ordering; + +NAMESPACE_PRIVATE_BEGIN + +template struct TCommonComparisonCategory { using Type = void; }; +template<> struct TCommonComparisonCategory<0> { using Type = strong_ordering; }; +template<> struct TCommonComparisonCategory<2> { using Type = partial_ordering; }; +template<> struct TCommonComparisonCategory<4> { using Type = weak_ordering; }; +template<> struct TCommonComparisonCategory<6> { using Type = partial_ordering; }; + +NAMESPACE_PRIVATE_END + +template +struct TCommonComparisonCategory + : NAMESPACE_PRIVATE::TCommonComparisonCategory<(0u | ... | + ( + TIsSame::Value ? 0u : + TIsSame::Value ? 4u : + TIsSame::Value ? 2u : 1u + ) + )> +{ }; + +template +concept CThreeWayComparesAs = CSameAs::Type, OrderingType>; + +template +concept CThreeWayComparable = CWeaklyEqualityComparableWith && CPartiallyOrderedWith && + requires(const TRemoveReference::Type& A, const TRemoveReference::Type& B) + { + { A <=> B } -> CThreeWayComparesAs; + }; + +template +concept CThreeWayComparableWith = CWeaklyEqualityComparableWith && CPartiallyOrderedWith && + CThreeWayComparable && CThreeWayComparable && + CCommonReferenceWith::Type&, const typename TRemoveReference::Type&> && + CThreeWayComparable::Type&, const typename TRemoveReference::Type&>::Type, OrderingType> && + requires(const TRemoveReference::Type& A, const TRemoveReference::Type& B) + { + { A <=> B } -> CThreeWayComparesAs; + { B <=> A } -> CThreeWayComparesAs; + }; + +template +struct TCompareThreeWayResult { }; + +template requires CThreeWayComparableWith +struct TCompareThreeWayResult +{ + using Type = decltype(DeclVal::Type&>() <=> DeclVal::Type&>()); +}; + +template requires CSameAs || CThreeWayComparable +struct TCompareThreeWay +{ + constexpr auto operator()(T&& LHS, T&& RHS) const + { + return Forward(LHS) <=> Forward(RHS); + } +}; + +template <> +struct TCompareThreeWay +{ + template requires CThreeWayComparableWith + constexpr auto operator()(T&& LHS, U&& RHS) const + { + return Forward(LHS) <=> Forward(RHS); + } +}; + +NAMESPACE_UNNAMED_BEGIN + +inline constexpr decltype(NAMESPACE_STD::strong_order) StrongOrder; +inline constexpr decltype(NAMESPACE_STD::weak_order) WeakOrder; +inline constexpr decltype(NAMESPACE_STD::partial_order) PartialOrder; +inline constexpr decltype(NAMESPACE_STD::compare_strong_order_fallback) CompareStrongOrderFallback; +inline constexpr decltype(NAMESPACE_STD::compare_weak_order_fallback) CompareWeakOrderFallback; +inline constexpr decltype(NAMESPACE_STD::compare_partial_order_fallback) ComparePartialOrderFallback; + +NAMESPACE_UNNAMED_END + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Templates/Optional.h b/Redcraft.Utility/Source/Public/Templates/Optional.h new file mode 100644 index 0000000..2f30864 --- /dev/null +++ b/Redcraft.Utility/Source/Public/Templates/Optional.h @@ -0,0 +1,223 @@ +#pragma once + +#include "CoreTypes.h" +#include "TypeTraits/TypeTraits.h" +#include "Miscellaneous/AssertionMacros.h" + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +template +struct TOptional +{ +public: + + using Type = T; + + constexpr TOptional() : bIsValid(false) { } + + template requires TIsConstructible::Value + constexpr explicit TOptional(EInPlace, Types&&... Args) + : bIsValid(true) + { + new(&Value) T(Forward(Args)...); + } + + template requires TIsConstructible::Value && !TIsSame::Type, EInPlace>::Value && !TIsSame::Type, TOptional>::Value + constexpr explicit(!TIsConvertible::Value) TOptional(U&& InValue) + : TOptional(InPlace, Forward(InValue)) + { } + + template requires TIsConstructible::Value + constexpr explicit(!TIsConvertible::Value) TOptional(const TOptional& InValue) + : bIsValid(InValue.bIsValid) + { + if (InValue.bIsValid) new(&Value) T(InValue.GetValue()); + } + + template requires TIsConstructible::Value + constexpr explicit(!TIsConvertible::Value) TOptional(TOptional&& InValue) + : bIsValid(InValue.bIsValid) + { + if (InValue.bIsValid) new(&Value) T(MoveTempIfPossible(InValue).GetValue()); + } + + constexpr ~TOptional() + { + Reset(); + } + + template requires TIsConstructible::Value + constexpr TOptional& operator=(const TOptional& InValue) + { + if (InValue == this) return *this; + + Reset(); + + if (InValue.bIsValid) + { + new(&Value) T(InValue.GetValue()); + bIsValid = true; + } + + return *this; + } + + template requires TIsConstructible::Value + constexpr TOptional& operator=(TOptional&& InValue) + { + if (InValue == this) return *this; + + Reset(); + + if (InValue.bIsValid) + { + new(&Value) T(MoveTempIfPossible(InValue).GetValue()); + bIsValid = true; + } + + return *this; + } + + template requires TIsConstructible::Value + constexpr TOptional& operator=(U&& InValue) + { + Reset(); + + new(&Value) T(MoveTempIfPossible(InValue)); + bIsValid = true; + + return *this; + } + + + template + constexpr T& Emplace(ArgsType&&... Args) + { + Reset(); + + T* Result = new(&Value) T(Forward(Args)...); + bIsValid = true; + + return *Result; + } + + constexpr bool IsValid() const { return bIsValid; } + constexpr explicit operator bool() const { return bIsValid; } + + constexpr T& GetValue() & { checkf(IsValid(), TEXT("It is an error to call GetValue() on an unset TOptional. Please either check IsSet() or use Get(DefaultValue) instead.")); return *(T*)&Value; } + constexpr T&& GetValue() && { checkf(IsValid(), TEXT("It is an error to call GetValue() on an unset TOptional. Please either check IsSet() or use Get(DefaultValue) instead.")); return *(T*)&Value; } + constexpr const T& GetValue() const& { checkf(IsValid(), TEXT("It is an error to call GetValue() on an unset TOptional. Please either check IsSet() or use Get(DefaultValue) instead.")); return *(T*)&Value; } + constexpr const T&& GetValue() const&& { checkf(IsValid(), TEXT("It is an error to call GetValue() on an unset TOptional. Please either check IsSet() or use Get(DefaultValue) instead.")); return *(T*)&Value; } + + constexpr const T* operator->() const { return &GetValue(); } + constexpr T* operator->() { return &GetValue(); } + + constexpr T& operator*() & { return GetValue(); } + constexpr T&& operator*() && { return GetValue(); } + constexpr const T& operator*() const& { return GetValue(); } + constexpr const T&& operator*() const&& { return GetValue(); } + + template + constexpr T Get(U&& DefaultValue) && { return IsValid() ? GetValue() : DefaultValue; } + + template + constexpr T Get(U&& DefaultValue) const& { return IsValid() ? GetValue() : DefaultValue; } + + constexpr void Reset() + { + if (bIsValid) + { + bIsValid = false; + + typedef T DestructOptionalType; + ((T*)&Value)->DestructOptionalType::~DestructOptionalType(); + } + } + +private: + + TAlignedStorage::Type Value; + bool bIsValid; + +}; + +template +TOptional(T) ->TOptional; + +template +constexpr bool operator==(const TOptional& LHS, const TOptional& RHS) +{ + if (LHS.IsValid() != LHS.IsValid()) return false; + if (LHS.IsValid() == false) return true; + return *LHS == *RHS; +} + +template +constexpr bool operator!=(const TOptional& LHS, const TOptional& RHS) +{ + if (LHS.IsValid() != LHS.IsValid()) return true; + if (LHS.IsValid() == false) return false; + return *LHS != *RHS; +} + +//template +//constexpr bool operator<(const TOptional&, const TOptional&); +//template +//constexpr bool operator>(const TOptional&, const TOptional&); +//template +//constexpr bool operator<=(const TOptional&, const TOptional&); +//template +//constexpr bool operator>=(const TOptional&, const TOptional&); + +//template constexpr bool operator==(const TOptional&, const U&); +//template constexpr bool operator==(const T&, const TOptional&); +//template constexpr bool operator!=(const TOptional&, const U&); +//template constexpr bool operator!=(const T&, const TOptional&); +//template constexpr bool operator<(const TOptional&, const U&); +//template constexpr bool operator<(const T&, const TOptional&); +//template constexpr bool operator>(const TOptional&, const U&); +//template constexpr bool operator>(const T&, const TOptional&); +//template constexpr bool operator<=(const TOptional&, const U&); +//template constexpr bool operator<=(const T&, const TOptional&); +//template constexpr bool operator>=(const TOptional&, const U&); +//template constexpr bool operator>=(const T&, const TOptional&); + +template +constexpr TOptional::Type> MakeOptional(T&& InValue) +{ + return TOptional::Type>(Forward(InValue)); +} + +template +constexpr TOptional MakeOptional(Types&&... Args) +{ + return TOptional(InPlace, Forward(Args)...); +} + +template +constexpr void Swap(TOptional& A, TOptional& B) +{ + if (!A && !B) return; + + if (A && !B) + { + B = A; + A.Reset(); + return; + } + + if (B && !A) + { + A = B; + B.Reset(); + return; + } + + Swap(*A, *B); +} + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Templates/Templates.h b/Redcraft.Utility/Source/Public/Templates/Templates.h index 67ceb48..a8938c4 100644 --- a/Redcraft.Utility/Source/Public/Templates/Templates.h +++ b/Redcraft.Utility/Source/Public/Templates/Templates.h @@ -6,3 +6,4 @@ #include "Templates/Noncopyable.h" #include "Templates/Invoke.h" #include "Templates/ReferenceWrapper.h" +#include "Templates/Compare.h" diff --git a/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h b/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h index e6758fa..77c6dca 100644 --- a/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h +++ b/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h @@ -9,6 +9,7 @@ NAMESPACE_MODULE_BEGIN(Utility) void REDCRAFTUTILITY_API TestTemplates(); void REDCRAFTUTILITY_API TestInvoke(); void REDCRAFTUTILITY_API TestReferenceWrapper(); +void REDCRAFTUTILITY_API TestCompare(); void REDCRAFTUTILITY_API TestMiscellaneous(); NAMESPACE_MODULE_END(Utility)