diff --git a/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp b/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp index 119442d..27ce98a 100644 --- a/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp @@ -20,6 +20,7 @@ void TestTemplates() TestReferenceWrapper(); TestOptional(); TestVariant(); + TestAny(); TestMiscTemplates(); } @@ -316,6 +317,210 @@ void TestVariant() } +void TestAny() +{ + struct FIntegral + { + int32 A; + FIntegral() { } + FIntegral(int32 InA) : A(InA) { } + bool operator==(FIntegral RHS) const { return A == RHS.A; } + }; + + struct FFloating + { + double A; + uint8 Pad[64]; + FFloating() { } + FFloating(double InA) : A(InA) { } + bool operator==(FFloating RHS) const { return A == RHS.A; } + }; + + struct FTracker + { + FTracker() { } + FTracker(const FTracker& InValue) { always_check_no_entry(); } + FTracker(FTracker&& InValue) { } + FTracker& operator=(const FTracker& InValue) { always_check_no_entry(); return *this; } + FTracker& operator=(FTracker&& InValue) { return *this; } + }; + + { + FAny TempA; + FAny TempB(Invalid); + FAny TempC(0); + FAny TempD(InPlaceType, 0); + FAny TempG(TempA); + FAny TempH(TempC); + + FAny TempK, TempL, TempM, TempN; + TempK = TempA; + TempL = TempD; + TempM = FAny(0); + TempN = FAny(Invalid); + + TempL = 303; + TempM = 404; + + FAny TempO; + TempO.Emplace(202); + TempO.Emplace(404); + + always_check(TempO); + always_check(TempO.IsValid()); + + always_check(TempO == 404); + always_check(TempO.GetValue() == 404); + always_check(TempO.Get(500) == 404); + + TempO.Reset(); + always_check(TempO.Get(500) == 500); + + int32 TempP = 200; + TempO = TempP; + TempO = 300; + + always_check(TempO == 300); + always_check(300 == TempO); + + Swap(TempD, TempA); + + always_check(!TempD.IsValid()); + always_check(0 == TempA); + } + + { + FAny TempA; + FAny TempB(Invalid); + FAny TempC(FIntegral(0)); + FAny TempD(InPlaceType, 0); + FAny TempG(TempA); + FAny TempH(TempC); + + FAny TempK, TempL, TempM, TempN; + TempK = TempA; + TempL = TempD; + TempM = FAny(FIntegral(0)); + TempN = FAny(Invalid); + + TempL = FIntegral(303); + TempM = FIntegral(404); + + FAny TempO; + TempO.Emplace(202); + TempO.Emplace(404); + + always_check(TempO); + always_check(TempO.IsValid()); + + always_check(TempO == FIntegral(404)); + always_check(TempO.GetValue() == FIntegral(404)); + always_check(TempO.Get(500) == FIntegral(404)); + + TempO.Reset(); + always_check(TempO.Get(500) == FIntegral(500)); + + FIntegral TempP = FIntegral(200); + TempO = TempP; + TempO = FIntegral(300); + + always_check(TempO == FIntegral(300)); + always_check(FIntegral(300) == TempO); + + Swap(TempD, TempA); + + always_check(!TempD.IsValid()); + always_check(FIntegral(0) == TempA); + } + + { + FAny TempA; + FAny TempB(Invalid); + FAny TempC(FFloating(0.0)); + FAny TempD(InPlaceType, 0.0); + FAny TempG(TempA); + FAny TempH(TempC); + + FAny TempK, TempL, TempM, TempN; + TempK = TempA; + TempL = TempD; + TempM = FAny(FFloating(0.0)); + TempN = FAny(Invalid); + + TempL = FFloating(303.0); + TempM = FFloating(404.0); + + FAny TempO; + TempO.Emplace(202.0); + TempO.Emplace(404.0); + + always_check(TempO); + always_check(TempO.IsValid()); + + always_check(TempO == FFloating(404.0)); + always_check(TempO.GetValue() == FFloating(404.0)); + always_check(TempO.Get(500.0) == FFloating(404.0)); + + TempO.Reset(); + always_check(TempO.Get(500.0) == FFloating(500.0)); + + FFloating TempP = FFloating(200.0); + TempO = TempP; + TempO = FFloating(300.0); + + always_check(TempO == FFloating(300.0)); + always_check(FFloating(300.0) == TempO); + + Swap(TempD, TempA); + + always_check(!TempD.IsValid()); + always_check(FFloating(0.0) == TempA); + } + + { + FAny TempA; + FAny TempB(InPlaceType, 0); + FAny TempC(InPlaceType, 0); + FAny TempD(InPlaceType, 0.0); + FAny TempE(InPlaceType); + + Swap(TempA, TempB); + Swap(TempA, TempC); + Swap(TempA, TempD); + Swap(TempA, TempE); + + Swap(TempB, TempA); + Swap(TempB, TempC); + Swap(TempB, TempD); + Swap(TempB, TempE); + + Swap(TempC, TempA); + Swap(TempC, TempB); + Swap(TempC, TempD); + Swap(TempC, TempE); + + Swap(TempD, TempA); + Swap(TempD, TempB); + Swap(TempD, TempC); + Swap(TempD, TempE); + + Swap(TempE, TempA); + Swap(TempE, TempB); + Swap(TempE, TempC); + Swap(TempE, TempD); + + always_check(TempA == FIntegral(0)); + always_check(TempB == FFloating(0.0)); + always_check(TempC.HoldsAlternative()); + always_check(TempD == Invalid); + always_check(TempE == int32(0)); + + FAny TempZ(Invalid); + TempZ = FAny(); + TempZ = FTracker(); + } +} + NAMESPACE_UNNAMED_BEGIN template diff --git a/Redcraft.Utility/Source/Public/Templates/Any.h b/Redcraft.Utility/Source/Public/Templates/Any.h new file mode 100644 index 0000000..83e0033 --- /dev/null +++ b/Redcraft.Utility/Source/Public/Templates/Any.h @@ -0,0 +1,450 @@ +#pragma once + +#include "CoreTypes.h" +#include "Memory/Memory.h" +#include "Templates/Utility.h" +#include "TypeTraits/TypeTraits.h" +#include "Miscellaneous/TypeInfo.h" +#include "Miscellaneous/AssertionMacros.h" + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +NAMESPACE_PRIVATE_BEGIN + +template +void* AnyCopyNew(const void* Source) +{ + if constexpr (!TIsCopyConstructible::Value) check_no_entry(); + else return new T(*reinterpret_cast(Source)); + return nullptr; +} + +using FAnyCopyNewFunc = void* (*)(const void*); + +template +void* AnyMoveNew(void* Source) +{ + if constexpr (!TIsMoveConstructible::Value) check_no_entry(); + else return new T(MoveTemp(*reinterpret_cast(Source))); + return nullptr; +} + +using FAnyMoveNewFunc = void* (*)(void*); + +template +void AnyDelete(void* InValue) +{ + delete reinterpret_cast(InValue); +} + +using FAnyDeleteFunc = void(*)(void*); + +template +void AnyDestroy(void* InValue) +{ + if constexpr (!TIsTriviallyDestructible::Value) + { + typedef T DestructOptionalType; + reinterpret_cast(InValue)->DestructOptionalType::~DestructOptionalType(); + } +} + +using FAnyDestroyFunc = void(*)(void*); + +template +void AnyCopyConstruct(void* Target, const void* Source) +{ + if constexpr (!TIsCopyConstructible::Value) check_no_entry(); + else new(reinterpret_cast(Target)) T(*reinterpret_cast(Source)); +} + +using FAnyCopyConstructFunc = void(*)(void*, const void*); + +template +void AnyMoveConstruct(void* Target, void* Source) +{ + if constexpr (!TIsMoveConstructible::Value) check_no_entry(); + else new(reinterpret_cast(Target)) T(MoveTemp(*reinterpret_cast(Source))); +} + +using FAnyMoveConstructFunc = void(*)(void*, void*); + +template +void AnyCopyAssign(void* Target, const void* Source) +{ + if constexpr (!TIsCopyAssignable::Value) check_no_entry(); + else *reinterpret_cast(Target) = *reinterpret_cast(Source); +} + +using FAnyCopyAssignFunc = void(*)(void*, const void*); + +template +void AnyMoveAssign(void* Target, void* Source) +{ + if constexpr (!TIsMoveAssignable::Value) check_no_entry(); + else *reinterpret_cast(Target) = MoveTemp(*reinterpret_cast(Source)); +} + +using FAnyMoveAssignFunc = void(*)(void*, void*); + +struct FAnyRTTI +{ + bool bIsInline; + FAnyCopyNewFunc CopyNew; + FAnyMoveNewFunc MoveNew; + FAnyDeleteFunc Delete; + FAnyDestroyFunc Destroy; + FAnyCopyConstructFunc CopyConstruct; + FAnyMoveConstructFunc MoveConstruct; + FAnyCopyAssignFunc CopyAssign; + FAnyMoveAssignFunc MoveAssign; +}; + +template +struct TAnyRTTIHelper +{ + static constexpr FAnyRTTI RTTI = + { + bInIsInline, + AnyCopyNew, + AnyMoveNew, + AnyDelete, + AnyDestroy, + AnyCopyConstruct, + AnyMoveConstruct, + AnyCopyAssign, + AnyMoveAssign, + }; +}; + +inline constexpr size_t ANY_DEFAULT_INLINE_SIZE = 64 - sizeof(FTypeInfo) - sizeof(const FAnyRTTI*); +inline constexpr size_t ANY_DEFAULT_INLINE_ALIGNMENT = Memory::MINIMUM_ALIGNMENT; + +NAMESPACE_PRIVATE_END + +template +struct TAny +{ + template + struct TIsInlineStorable : TBoolConstant { }; + + template + struct TIsTriviallyStorable : TBoolConstant::Value && TIsTrivial::Value && TIsTriviallyCopyable::Value> { }; + + constexpr TAny() : TypeInfo(Typeid(void)), RTTI(nullptr) { } + + TAny(FInvalid) : TAny() { } + + TAny(const TAny& InValue) + : TypeInfo(InValue.TypeInfo), RTTI(InValue.RTTI) + { + if (!IsValid()) return; + + if (IsTrivial()) Memory::Memcpy(InlineValue, InValue.InlineValue); + else if (IsInline()) RTTI->CopyConstruct(GetData(), InValue.GetData()); + else DynamicValue = RTTI->CopyNew(InValue.GetData()); + } + + TAny(TAny&& InValue) + : TypeInfo(InValue.TypeInfo), RTTI(InValue.RTTI) + { + if (!IsValid()) return; + + if (IsTrivial()) Memory::Memcpy(InlineValue, InValue.InlineValue); + else if (IsInline()) RTTI->MoveConstruct(GetData(), InValue.GetData()); + else + { + DynamicValue = InValue.DynamicValue; + InValue.TypeInfo = Typeid(void); + } + } + + template requires TIsObject::Type>::Value + && (!TIsArray::Type>::Value) && TIsDestructible::Type>::Value + && TIsConstructible::Type, Types...>::Value + explicit TAny(TInPlaceType, Types&&... Args) + : TypeInfo(Typeid(typename TDecay::Type)) + { + using SelectedType = typename TDecay::Type; + + if constexpr (TIsTriviallyStorable::Value) + { + new(&InlineValue) SelectedType(Forward(Args)...); + RTTI = nullptr; + } + else if constexpr (TIsInlineStorable::Value) + { + new(&InlineValue) SelectedType(Forward(Args)...); + RTTI = &NAMESPACE_PRIVATE::TAnyRTTIHelper::RTTI; + } + else + { + DynamicValue = new SelectedType(Forward(Args)...); + RTTI = &NAMESPACE_PRIVATE::TAnyRTTIHelper::RTTI; + } + } + + template requires (!TIsSame::Type, TAny>::Value) && (!TIsInPlaceTypeSpecialization::Type>::Value) + && TIsObject::Type>::Value && (!TIsArray::Type>::Value) && TIsDestructible::Type>::Value + && TIsConstructible::Type, T&&>::Value + TAny(T&& InValue) : TAny(InPlaceType::Type>, Forward(InValue)) + { } + + ~TAny() + { + Reset(); + } + + TAny& operator=(const TAny& InValue) + { + if (!InValue.IsValid()) + { + Reset(); + } + else if (TypeInfo == InValue.TypeInfo) + { + if (IsTrivial()) Memory::Memcpy(InlineValue, InValue.InlineValue); + else RTTI->CopyAssign(GetData(), InValue.GetData()); + } + else + { + Reset(); + + TypeInfo = InValue.TypeInfo; + RTTI = InValue.RTTI; + + if (IsTrivial()) Memory::Memcpy(InlineValue, InValue.InlineValue); + else if (IsInline()) RTTI->CopyConstruct(GetData(), InValue.GetData()); + else DynamicValue = RTTI->CopyNew(InValue.GetData()); + } + + return *this; + } + + TAny& operator=(TAny&& InValue) + { + if (!InValue.IsValid()) + { + Reset(); + } + else if (TypeInfo == InValue.TypeInfo) + { + if (IsTrivial()) Memory::Memcpy(InlineValue, InValue.InlineValue); + else if (IsInline()) RTTI->MoveAssign(GetData(), InValue.GetData()); + else + { + RTTI->Delete(DynamicValue); + DynamicValue = InValue.DynamicValue; + InValue.TypeInfo = Typeid(void); + } + } + else + { + Reset(); + + TypeInfo = InValue.TypeInfo; + RTTI = InValue.RTTI; + + if (IsTrivial()) Memory::Memcpy(InlineValue, InValue.InlineValue); + else if (IsInline()) RTTI->MoveConstruct(GetData(), InValue.GetData()); + else DynamicValue = RTTI->MoveNew(InValue.GetData()); + } + + return *this; + } + + template requires (!TIsSame::Type, TAny>::Value) && (!TIsInPlaceTypeSpecialization::Type>::Value) + && TIsObject::Type>::Value && (!TIsArray::Type>::Value) && TIsDestructible::Type>::Value + && TIsConstructible::Type, T&&>::Value + TAny& operator=(T&& InValue) + { + using SelectedType = typename TDecay::Type; + + if (TypeInfo == Typeid(SelectedType)) + { + if constexpr (TIsTriviallyStorable::Value) + Memory::Memcpy(&InlineValue, &InValue, sizeof(SelectedType)); + else GetValue() = Forward(InValue); + } + else + { + Reset(); + + TypeInfo = Typeid(SelectedType); + + if constexpr (TIsTriviallyStorable::Value) + { + new(&InlineValue) SelectedType(Forward(InValue)); + RTTI = nullptr; + } + else if constexpr (TIsInlineStorable::Value) + { + new(&InlineValue) SelectedType(Forward(InValue)); + RTTI = &NAMESPACE_PRIVATE::TAnyRTTIHelper::RTTI; + } + else + { + DynamicValue = new SelectedType(Forward(InValue)); + RTTI = &NAMESPACE_PRIVATE::TAnyRTTIHelper::RTTI; + } + } + + return *this; + } + + template requires TIsObject::Type>::Value + && (!TIsArray::Type>::Value) && TIsDestructible::Type>::Value + && TIsConstructible::Type, T&&>::Value + typename TDecay::Type& Emplace(Types&&... Args) + { + Reset(); + + using SelectedType = typename TDecay::Type; + + TypeInfo = Typeid(SelectedType); + + if constexpr (TIsTriviallyStorable::Value) + { + new(&InlineValue) SelectedType(Forward(Args)...); + RTTI = nullptr; + } + else if constexpr (TIsInlineStorable::Value) + { + new(&InlineValue) SelectedType(Forward(Args)...); + RTTI = &NAMESPACE_PRIVATE::TAnyRTTIHelper::RTTI; + } + else + { + DynamicValue = new SelectedType(Forward(Args)...); + RTTI = &NAMESPACE_PRIVATE::TAnyRTTIHelper::RTTI; + } + + return GetValue(); + } + + constexpr FTypeInfo GetTypeInfo() const { return TypeInfo; } + constexpr bool IsValid() const { return TypeInfo != Typeid(void); } + constexpr explicit operator bool() const { return TypeInfo != Typeid(void); } + constexpr bool IsInline() const { return RTTI != nullptr ? RTTI->bIsInline : true; } + constexpr bool IsTrivial() const { return RTTI == nullptr; } + + template constexpr bool HoldsAlternative() const { return IsValid() ? TypeInfo == Typeid(T) : false; } + + constexpr void* GetData() { return IsInline() ? &InlineValue : DynamicValue; } + constexpr const void* GetData() const { return IsInline() ? &InlineValue : DynamicValue; } + + template constexpr T& GetValue() & { checkf(HoldsAlternative(), TEXT("It is an error to call GetValue() on an wrong TAny. Please either check HoldsAlternative() or use Get(DefaultValue) instead.")); return *reinterpret_cast< T*>(GetData()); } + template constexpr T&& GetValue() && { checkf(HoldsAlternative(), TEXT("It is an error to call GetValue() on an wrong TAny. Please either check HoldsAlternative() or use Get(DefaultValue) instead.")); return MoveTemp(*reinterpret_cast< T*>(GetData())); } + template constexpr const T& GetValue() const& { checkf(HoldsAlternative(), TEXT("It is an error to call GetValue() on an wrong TAny. Please either check HoldsAlternative() or use Get(DefaultValue) instead.")); return *reinterpret_cast(GetData()); } + template constexpr const T&& GetValue() const&& { checkf(HoldsAlternative(), TEXT("It is an error to call GetValue() on an wrong TAny. Please either check HoldsAlternative() or use Get(DefaultValue) instead.")); return MoveTemp(*reinterpret_cast(GetData())); } + + template constexpr T Get(T&& DefaultValue) && { return HoldsAlternative() ? GetValue() : DefaultValue; } + template constexpr T Get(T&& DefaultValue) const& { return HoldsAlternative() ? GetValue() : DefaultValue; } + + void Reset() + { + if (!IsValid()) return; + + TypeInfo = Typeid(void); + + if (IsTrivial()); + else if (IsInline()) RTTI->Destroy(&InlineValue); + else RTTI->Delete(DynamicValue); + + RTTI = nullptr; + } + +private: + + union + { + TAlignedStorage::Type InlineValue; + void* DynamicValue; + }; + + FTypeInfo TypeInfo; + const NAMESPACE_PRIVATE::FAnyRTTI* RTTI; + +}; + +template +constexpr bool operator==(const TAny& LHS, const T& RHS) +{ + return LHS.template HoldsAlternative() ? LHS.template GetValue() == RHS : false; +} + +template +constexpr bool operator!=(const TAny& LHS, const T& RHS) +{ + return LHS.template HoldsAlternative() ? LHS.template GetValue() != RHS : true; +} + +template +constexpr bool operator==(const T& LHS, const TAny& RHS) +{ + return RHS.template HoldsAlternative() ? LHS == RHS.template GetValue() : false; +} + +template +constexpr bool operator!=(const T& LHS, const TAny& RHS) +{ + return RHS.template HoldsAlternative() ? LHS != RHS.template GetValue() : true; +} + +template +constexpr bool operator==(const TAny& LHS, FInvalid) +{ + return !LHS.IsValid(); +} + +template +constexpr bool operator!=(const TAny& LHS, FInvalid) +{ + return LHS.IsValid(); +} + +template +constexpr bool operator==(FInvalid, const TAny& RHS) +{ + return !RHS.IsValid(); +} + +template +constexpr bool operator!=(FInvalid, const TAny& RHS) +{ + return RHS.IsValid(); +} + +template +constexpr void Swap(TAny& A, TAny& B) +{ + if (!A && !B) return; + + if (A && !B) + { + B = MoveTemp(A); + A.Reset(); + return; + } + + if (B && !A) + { + A = MoveTemp(B); + B.Reset(); + return; + } + + TAny Temp = MoveTemp(A); + A = MoveTemp(B); + B = MoveTemp(Temp); +} + +using FAny = TAny; + +static_assert(sizeof(FAny) == 64, "The byte size of FAny is unexpected"); + +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 e29a6cd..5024671 100644 --- a/Redcraft.Utility/Source/Public/Templates/Templates.h +++ b/Redcraft.Utility/Source/Public/Templates/Templates.h @@ -8,3 +8,4 @@ #include "Templates/ReferenceWrapper.h" #include "Templates/Optional.h" #include "Templates/Variant.h" +#include "Templates/Any.h" diff --git a/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h b/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h index c073d1f..dd0ffba 100644 --- a/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h +++ b/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h @@ -13,6 +13,7 @@ REDCRAFTUTILITY_API void TestInvoke(); REDCRAFTUTILITY_API void TestReferenceWrapper(); REDCRAFTUTILITY_API void TestOptional(); REDCRAFTUTILITY_API void TestVariant(); +REDCRAFTUTILITY_API void TestAny(); REDCRAFTUTILITY_API void TestMiscTemplates(); NAMESPACE_END(Testing)