#pragma once #include "CoreTypes.h" #include "Memory/Memory.h" #include "Memory/Alignment.h" #include "Templates/Utility.h" #include "Templates/TypeHash.h" #include "TypeTraits/TypeTraits.h" #include "Miscellaneous/Compare.h" #include "Miscellaneous/AssertionMacros.h" NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) // NOTE: In the STL, the assignment operation of the std::any type uses the copy-and-swap idiom // instead of directly calling the assignment operation of the contained value. // The purpose of this is as follows: // 1) the copy assignment might not exist. // 2) the typical case is that the objects are different. // 3) it is less exception-safe // But we don't follow the the copy-and-swap idiom, because we assume that no function throws an exception. NAMESPACE_PRIVATE_BEGIN template concept CFAnyPlaceable = CDestructible> && CCopyConstructible> && CMoveConstructible>; NAMESPACE_PRIVATE_END /** * The class any describes a type-safe container for single values of any copy and move constructible type. * An object of class any stores an instance of any type that satisfies the constructor requirements or is empty, * and this is referred to as the state of the class any object. The stored instance is called the contained object. */ class alignas(16) FAny final { public: /** Constructs an empty object. */ FORCEINLINE constexpr FAny() { Invalidate(); } /** Constructs an empty object. */ FORCEINLINE constexpr FAny(FInvalid) : FAny() { } /** Copies content of other into a new instance. This may use the object's copy constructor. */ FAny(const FAny& InValue) : TypeInfo(InValue.TypeInfo) { if (!IsValid()) return; switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memcpy(TrivialStorage.Internal, InValue.TrivialStorage.Internal); break; case ERepresentation::Small: SmallStorage.RTTI = InValue.SmallStorage.RTTI; SmallStorage.RTTI->CopyConstruct(&SmallStorage.Internal, &InValue.SmallStorage.Internal); break; case ERepresentation::Big: BigStorage.RTTI = InValue.BigStorage.RTTI; BigStorage.External = Memory::Malloc(BigStorage.RTTI->TypeSize, BigStorage.RTTI->TypeAlignment); BigStorage.RTTI->CopyConstruct(BigStorage.External, InValue.BigStorage.External); break; default: check_no_entry(); } } /** Moves content of other into a new instance. This may use the object's move constructor. */ FAny(FAny&& InValue) : TypeInfo(InValue.TypeInfo) { if (!IsValid()) return; switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memmove(TrivialStorage.Internal, InValue.TrivialStorage.Internal); break; case ERepresentation::Small: SmallStorage.RTTI = InValue.SmallStorage.RTTI; SmallStorage.RTTI->MoveConstruct(&SmallStorage.Internal, &InValue.SmallStorage.Internal); break; case ERepresentation::Big: BigStorage.RTTI = InValue.BigStorage.RTTI; BigStorage.External = InValue.BigStorage.External; InValue.Invalidate(); break; default: check_no_entry(); } } /** Constructs an object with initial content an object of type TDecay, direct-initialized from Forward(InValue). */ template requires (!CSameAs> && !CTInPlaceType> && NAMESPACE_PRIVATE::CFAnyPlaceable && CConstructibleFrom, T&&>) FORCEINLINE FAny(T&& InValue) : FAny(InPlaceType, Forward(InValue)) { } /** Constructs an object with initial content an object of type TDecay, direct-non-list-initialized from Forward(Args).... */ template requires (NAMESPACE_PRIVATE::CFAnyPlaceable && CConstructibleFrom, Ts&&...>) FORCEINLINE explicit FAny(TInPlaceType, Ts&&... Args) { EmplaceImpl(Forward(Args)...); } /** Constructs an object with initial content an object of type TDecay, direct-non-list-initialized from IL, Forward(Args).... */ template requires (NAMESPACE_PRIVATE::CFAnyPlaceable && CConstructibleFrom, initializer_list, Ts&&...>) FORCEINLINE explicit FAny(TInPlaceType, initializer_list IL, Ts&&... Args) { EmplaceImpl(IL, Forward(Args)...); } /** Destroys the contained object, if any, as if by a call to Reset(). */ FORCEINLINE ~FAny() { Destroy(); } /** Assigns by copying the state of 'InValue'. This may use the object's copy constructor or copy assignment operator. */ FAny& operator=(const FAny& InValue) { if (&InValue == this) UNLIKELY return *this; if (!InValue.IsValid()) { Reset(); } else if (GetTypeInfo() == InValue.GetTypeInfo()) { switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memcpy(TrivialStorage.Internal, InValue.TrivialStorage.Internal); break; case ERepresentation::Small: SmallStorage.RTTI = InValue.SmallStorage.RTTI; SmallStorage.RTTI->CopyAssign(&SmallStorage.Internal, &InValue.SmallStorage.Internal); break; case ERepresentation::Big: BigStorage.RTTI = InValue.BigStorage.RTTI; BigStorage.RTTI->CopyAssign(BigStorage.External, InValue.BigStorage.External); break; default: check_no_entry(); } } else { Destroy(); TypeInfo = InValue.TypeInfo; switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memcpy(TrivialStorage.Internal, InValue.TrivialStorage.Internal); break; case ERepresentation::Small: SmallStorage.RTTI = InValue.SmallStorage.RTTI; SmallStorage.RTTI->CopyConstruct(&SmallStorage.Internal, &InValue.SmallStorage.Internal); break; case ERepresentation::Big: BigStorage.RTTI = InValue.BigStorage.RTTI; BigStorage.External = Memory::Malloc(BigStorage.RTTI->TypeSize, BigStorage.RTTI->TypeAlignment); BigStorage.RTTI->CopyConstruct(BigStorage.External, InValue.BigStorage.External); break; default: check_no_entry(); } } return *this; } /** Assigns by moving the state of 'InValue'. This may use the object's move constructor or move assignment operator. */ FAny& operator=(FAny&& InValue) { if (&InValue == this) UNLIKELY return *this; if (!InValue.IsValid()) { Reset(); } else if (GetTypeInfo() == InValue.GetTypeInfo()) { switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memmove(TrivialStorage.Internal, InValue.TrivialStorage.Internal); break; case ERepresentation::Small: SmallStorage.RTTI = InValue.SmallStorage.RTTI; SmallStorage.RTTI->MoveAssign(&SmallStorage.Internal, &InValue.SmallStorage.Internal); break; case ERepresentation::Big: Destroy(); BigStorage.RTTI = InValue.BigStorage.RTTI; BigStorage.External = InValue.BigStorage.External; InValue.Invalidate(); break; default: check_no_entry(); } } else { Destroy(); TypeInfo = InValue.TypeInfo; switch (GetRepresentation()) { case ERepresentation::Empty: break; case ERepresentation::Trivial: Memory::Memmove(TrivialStorage.Internal, InValue.TrivialStorage.Internal); break; case ERepresentation::Small: SmallStorage.RTTI = InValue.SmallStorage.RTTI; SmallStorage.RTTI->MoveConstruct(&SmallStorage.Internal, &InValue.SmallStorage.Internal); break; case ERepresentation::Big: BigStorage.RTTI = InValue.BigStorage.RTTI; BigStorage.External = InValue.BigStorage.External; InValue.Invalidate(); break; default: check_no_entry(); } } return *this; } /** Assigns the type and value of 'InValue'. This may use the object's constructor or assignment operator. */ template requires (!CSameAs> && !CTInPlaceType> && NAMESPACE_PRIVATE::CFAnyPlaceable && CConstructibleFrom, T&&>) FORCEINLINE FAny& operator=(T&& InValue) { using DecayedType = TDecay; if constexpr (CAssignableFrom) { if (HoldsAlternative()) { GetValue() = Forward(InValue); return *this; } } Destroy(); EmplaceImpl(Forward(InValue)); return *this; } /** Check if the contained value is equivalent to 'InValue'. */ template requires (!CSameAs> && NAMESPACE_PRIVATE::CFAnyPlaceable && CEqualityComparable) NODISCARD FORCEINLINE constexpr bool operator==(const T& InValue) const& { return HoldsAlternative() ? GetValue() == InValue : false; } /** Check that the contained value is in ordered relationship with 'InValue'. */ template requires (!CSameAs> && NAMESPACE_PRIVATE::CFAnyPlaceable && CSynthThreeWayComparable) NODISCARD FORCEINLINE constexpr partial_ordering operator<=>(const T& InValue) const& { return HoldsAlternative() ? SynthThreeWayCompare(GetValue(), InValue) : partial_ordering::unordered; } /** @return true if instance does not contain a value, otherwise false. */ NODISCARD FORCEINLINE constexpr bool operator==(FInvalid) const& { return !IsValid(); } /** * Changes the contained object to one of type TDecay constructed from the arguments. * First destroys the current contained object (if any) by Reset(), then constructs an object of type * TDecay, direct-non-list-initialized from Forward(Args)..., as the contained object. * * @param Args - The arguments to be passed to the constructor of the contained object. * * @return A reference to the new contained object. */ template requires (NAMESPACE_PRIVATE::CFAnyPlaceable && CConstructibleFrom, Ts&&...>) FORCEINLINE TDecay& Emplace(Ts&&... Args) { Destroy(); EmplaceImpl(Forward(Args)...); return GetValue>(); } /** * Changes the contained object to one of type TDecay constructed from the arguments. * First destroys the current contained object (if any) by Reset(), then constructs an object of type * TDecay, direct-non-list-initialized from IL, Forward(Args)..., as the contained object. * * @param IL, Args - The arguments to be passed to the constructor of the contained object. * * @return A reference to the new contained object. */ template requires (NAMESPACE_PRIVATE::CFAnyPlaceable && CConstructibleFrom, initializer_list, Ts&&...>) FORCEINLINE TDecay& Emplace(initializer_list IL, Ts&&... Args) { Destroy(); EmplaceImpl(IL, Forward(Args)...); return GetValue>(); } /** @return The typeid of the contained value if instance is non-empty, otherwise typeid(void). */ NODISCARD FORCEINLINE constexpr const type_info& GetTypeInfo() const { return IsValid() ? GetTypeInfoImpl() : typeid(void); } /** @return true if instance contains a value, otherwise false. */ NODISCARD FORCEINLINE constexpr bool IsValid() const { return TypeInfo != 0; } NODISCARD FORCEINLINE constexpr explicit operator bool() const { return TypeInfo != 0; } /** @return true if the any currently holds the alternative 'T', false otherwise. */ template NODISCARD FORCEINLINE constexpr bool HoldsAlternative() const { return IsValid() ? GetTypeInfo() == typeid(T) : false; } /** @return The contained object. */ template requires (CSameAs> && NAMESPACE_PRIVATE::CFAnyPlaceable) NODISCARD FORCEINLINE 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*>(GetStorage()); } /** @return The contained object. */ template requires (CSameAs> && NAMESPACE_PRIVATE::CFAnyPlaceable) NODISCARD FORCEINLINE 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*>(GetStorage())); } /** @return The contained object. */ template requires (CSameAs> && NAMESPACE_PRIVATE::CFAnyPlaceable) NODISCARD FORCEINLINE 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(GetStorage()); } /** @return The contained object. */ template requires (CSameAs> && NAMESPACE_PRIVATE::CFAnyPlaceable) NODISCARD FORCEINLINE 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(GetStorage())); } /** @return The contained object when HoldsAlternative() returns true, 'DefaultValue' otherwise. */ template requires (CSameAs> && NAMESPACE_PRIVATE::CFAnyPlaceable) NODISCARD FORCEINLINE constexpr T& Get( T& DefaultValue) & { return HoldsAlternative() ? GetValue() : DefaultValue; } /** @return The contained object when HoldsAlternative() returns true, 'DefaultValue' otherwise. */ template requires (CSameAs> && NAMESPACE_PRIVATE::CFAnyPlaceable) NODISCARD FORCEINLINE constexpr const T& Get(const T& DefaultValue) const& { return HoldsAlternative() ? GetValue() : DefaultValue; } /** If not empty, destroys the contained object. */ FORCEINLINE void Reset() { Destroy(); Invalidate(); } /** Overloads the Swap algorithm for FAny. */ friend void Swap(FAny& A, FAny& B) { if (!A.IsValid() && !B.IsValid()) return; if (A.IsValid() && !B.IsValid()) { B = MoveTemp(A); A.Reset(); } else if (!A.IsValid() && B.IsValid()) { A = MoveTemp(B); B.Reset(); } else { FAny Temp = MoveTemp(A); A = MoveTemp(B); B = MoveTemp(Temp); } } private: struct FRTTI { const size_t TypeSize; const size_t TypeAlignment; using FCopyConstruct = void(*)(void*, const void*); using FMoveConstruct = void(*)(void*, void*); using FCopyAssign = void(*)(void*, const void*); using FMoveAssign = void(*)(void*, void*); using FDestruct = void(*)(void* ); using FSwapObject = void(*)(void*, void*); const FCopyConstruct CopyConstruct; const FMoveConstruct MoveConstruct; const FCopyAssign CopyAssign; const FMoveAssign MoveAssign; const FDestruct Destruct; const FSwapObject SwapObject; template FORCEINLINE constexpr FRTTI(TInPlaceType) : TypeSize( sizeof(T)), TypeAlignment(alignof(T)) , CopyConstruct( [](void* A, const void* B) { new (A) T(*reinterpret_cast(B)); } ) , MoveConstruct( [](void* A, void* B) { new (A) T(MoveTemp(*reinterpret_cast(B))); } ) , CopyAssign( [](void* A, const void* B) { if constexpr (CCopyAssignable) { *reinterpret_cast(A) = *reinterpret_cast(B); } else { reinterpret_cast(A)->~T(); new (A) T(*reinterpret_cast(B)); } } ) , MoveAssign( [](void* A, void* B) { if constexpr (CMoveAssignable) { *reinterpret_cast(A) = MoveTemp(*reinterpret_cast(B)); } else { reinterpret_cast(A)->~T(); new (A) T(MoveTemp(*reinterpret_cast(B))); } } ) , Destruct( [](void* A) { reinterpret_cast(A)->~T(); } ) , SwapObject{ [](void* A, void* B) { if constexpr (CSwappable) { Swap(*reinterpret_cast(A), *reinterpret_cast(B)); } else { TAlignedStorage TempBuffer; new (&TempBuffer) T(MoveTemp(*reinterpret_cast(A))); reinterpret_cast(A)->~T(); new (A) T(MoveTemp(*reinterpret_cast(B))); reinterpret_cast(B)->~T(); new (B) T(MoveTemp(*reinterpret_cast(&TempBuffer))); reinterpret_cast(&TempBuffer)->~T(); } } } { } }; struct FTrivialStorage { uint8 Internal[64 - sizeof(uintptr)]; }; struct FSmallStorage { uint8 Internal[sizeof(FTrivialStorage) - sizeof(const FRTTI*)]; const FRTTI* RTTI; }; struct FBigStorage { uint8 Padding[sizeof(FTrivialStorage) - sizeof(void*) - sizeof(const FRTTI*)]; void* External; const FRTTI* RTTI; }; static_assert(sizeof(FTrivialStorage) == sizeof(FSmallStorage)); static_assert(sizeof(FTrivialStorage) == sizeof( FBigStorage)); static_assert(alignof(type_info) >= 4); static constexpr uintptr_t RepresentationMask = 3; enum class ERepresentation : uintptr { Empty = 0, // EmptyType Trivial = 1, // TrivialStorage Small = 2, // SmallStorage Big = 3, // BigStorage }; union { FTrivialStorage TrivialStorage; FSmallStorage SmallStorage; FBigStorage BigStorage; }; uintptr TypeInfo; FORCEINLINE ERepresentation GetRepresentation() const { return static_cast(TypeInfo & RepresentationMask); } FORCEINLINE const type_info& GetTypeInfoImpl() const { return *reinterpret_cast(TypeInfo & ~RepresentationMask); } FORCEINLINE void* GetStorage() { switch (GetRepresentation()) { case ERepresentation::Empty: return nullptr; case ERepresentation::Trivial: return &TrivialStorage.Internal; case ERepresentation::Small: return &SmallStorage.Internal; case ERepresentation::Big: return BigStorage.External; default: check_no_entry(); return nullptr; } } FORCEINLINE const void* GetStorage() const { switch (GetRepresentation()) { case ERepresentation::Empty: return nullptr; case ERepresentation::Trivial: return &TrivialStorage.Internal; case ERepresentation::Small: return &SmallStorage.Internal; case ERepresentation::Big: return BigStorage.External; default: check_no_entry(); return nullptr; } } template void EmplaceImpl(Ts&&... Args) { using DecayedType = TDecay; TypeInfo = reinterpret_cast(&typeid(DecayedType)); if constexpr (CEmpty && CTrivial) return; // ERepresentation::Empty constexpr bool bIsTriviallyStorable = sizeof(DecayedType) <= sizeof(TrivialStorage.Internal) && alignof(DecayedType) <= alignof(FAny) && CTriviallyCopyable; constexpr bool bIsSmallStorable = sizeof(DecayedType) <= sizeof( SmallStorage.Internal) && alignof(DecayedType) <= alignof(FAny); static constexpr const FRTTI SelectedRTTI(InPlaceType); if constexpr (bIsTriviallyStorable) { new (&TrivialStorage.Internal) DecayedType(Forward(Args)...); TypeInfo |= static_cast(ERepresentation::Trivial); } else if constexpr (bIsSmallStorable) { new (&SmallStorage.Internal) DecayedType(Forward(Args)...); SmallStorage.RTTI = &SelectedRTTI; TypeInfo |= static_cast(ERepresentation::Small); } else { BigStorage.External = Memory::Malloc(sizeof(DecayedType), alignof(DecayedType)); new (BigStorage.External) DecayedType(Forward(Args)...); BigStorage.RTTI = &SelectedRTTI; TypeInfo |= static_cast(ERepresentation::Big); } } void Destroy() { if (!IsValid()) return; switch (GetRepresentation()) { case ERepresentation::Empty: case ERepresentation::Trivial: break; case ERepresentation::Small: SmallStorage.RTTI->Destruct(&SmallStorage.Internal); break; case ERepresentation::Big: BigStorage.RTTI->Destruct(BigStorage.External); Memory::Free(BigStorage.External); break; default: check_no_entry(); } } FORCEINLINE constexpr void Invalidate() { TypeInfo = 0; } }; static_assert(sizeof(FAny) == 64, "The byte size of FAny is unexpected"); static_assert(alignof(FAny) == 16, "The byte alignment of FAny is unexpected"); NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END