From d8a4908a8887d1a6cc2cbb35c7232ed13d55d1c6 Mon Sep 17 00:00:00 2001 From: _Redstone_c_ Date: Thu, 31 Mar 2022 09:39:30 +0800 Subject: [PATCH] feat(templates): add TTuple and the corresponding testing --- .../Private/Testing/TemplatesTesting.cpp | 376 +++++++++++ .../Source/Public/Templates/Templates.h | 1 + .../Source/Public/Templates/Tuple.h | 621 ++++++++++++++++++ .../Source/Public/Templates/Utility.h | 8 + .../Source/Public/Testing/TemplatesTesting.h | 1 + 5 files changed, 1007 insertions(+) create mode 100644 Redcraft.Utility/Source/Public/Templates/Tuple.h diff --git a/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp b/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp index a5f01e5..6c30e6c 100644 --- a/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp @@ -21,6 +21,7 @@ void TestTemplates() TestOptional(); TestVariant(); TestAny(); + TestTuple(); TestMiscTemplates(); } @@ -524,6 +525,381 @@ void TestAny() } } +void TestTuple() +{ + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + + always_check((TIsSame&&>().GetValue<0>()), int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + + always_check((TIsSame&&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&>::Value)); + + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), volatile int&>::Value)); + always_check((TIsSame&>().GetValue<0>()), const volatile int&>::Value)); + + always_check((TIsSame&&>().GetValue<0>()), int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), volatile int&&>::Value)); + always_check((TIsSame&&>().GetValue<0>()), const volatile int&&>::Value)); + + always_check((TIsSame>::Type, double>::Value)); + always_check((TIsSame>::Type, float&>::Value)); + always_check((TIsSame>::Type, char&&>::Value)); + always_check((TIsSame>::Type, const double>::Value)); + always_check((TIsSame>::Type, const float&>::Value)); + always_check((TIsSame>::Type, const char&&>::Value)); + always_check((TIsSame>::Type, volatile double>::Value)); + always_check((TIsSame>::Type, volatile float&>::Value)); + always_check((TIsSame>::Type, volatile char&&>::Value)); + always_check((TIsSame>::Type, const volatile double>::Value)); + always_check((TIsSame>::Type, const volatile float&>::Value)); + always_check((TIsSame>::Type, const volatile char&&>::Value)); + + always_check((TTupleElementIndex>::Value == 0)); + always_check((TTupleElementIndex>::Value == 1)); + always_check((TTupleElementIndex>::Value == 2)); + always_check((TTupleElementIndex>::Value == 0)); + always_check((TTupleElementIndex>::Value == 1)); + always_check((TTupleElementIndex>::Value == 2)); + always_check((TTupleElementIndex>::Value == 0)); + always_check((TTupleElementIndex>::Value == 1)); + always_check((TTupleElementIndex>::Value == 2)); + always_check((TTupleElementIndex>::Value == 0)); + always_check((TTupleElementIndex>::Value == 1)); + always_check((TTupleElementIndex>::Value == 2)); + + always_check((TTupleElementIndex>::Value == INDEX_NONE)); + +// always_check((TIsSame::Type, double>::Value)); + +// always_check((TTupleElementIndex::Value == 0)); + +// always_check((TIsSame>::Type, double>::Value)); + + { + using Type = TTuple; + + Type Temp; + + Temp.First = 0; + Temp.Second = 0; + Temp.Third = 0; + Temp.Fourth = 0; + Temp.Fifth = 0; + Temp.Sixth = 0; + Temp.Seventh = 0; + Temp.Eighth = 0; + Temp.Ninth = 0; + Temp.Tenth = 0; + Temp.Eleventh = 0; + Temp.Twelfth = 0; + Temp.Thirteenth = 0; + Temp.Fourteenth = 0; + Temp.Fifteenth = 0; + Temp.Sixteenth = 0; + + always_check(TIsDefaultConstructible::Value); + always_check(TIsTriviallyDefaultConstructible::Value); + always_check(TIsConstructible::Value); + always_check(TIsTriviallyConstructible::Value); + always_check(TIsCopyConstructible::Value); + always_check(TIsTriviallyCopyConstructible::Value); + always_check(TIsMoveConstructible::Value); + always_check(TIsTriviallyMoveConstructible::Value); + always_check(TIsCopyAssignable::Value); + always_check(TIsTriviallyCopyAssignable::Value); + always_check(TIsMoveAssignable::Value); + always_check(TIsTriviallyMoveAssignable::Value); + always_check(TIsDestructible::Value); + always_check(TIsTriviallyDestructible::Value); + } + + { + TTuple TempA(0, 1); + TTuple TempB = { 0, 1 }; + TTuple TempC = TempB; + TTuple TempD = MoveTemp(TempB); + TTuple TempE, TempF; + TempE = TempC; + TempF = MoveTemp(TempD); + always_check(TempC.GetValue<0>() == 0); + always_check(TempC.GetValue() == 0); + } + + { + TTuple TempA = MakeTuple(1, 2, 3); + int32 TempB; + Tie(Ignore, TempB, Ignore) = TempA; + always_check(TempB == 2); + TTuple TempC = ForwardAsTuple(TempB); + TempC.GetValue<0>() = 4; + always_check(TempB == 4); + } + + struct FTracker + { + int8 Flag; + FTracker(int8 InFlag) : Flag(InFlag) { } + FTracker(const FTracker& InValue) { Flag = InValue.Flag - 1; always_check(!Flag); } + FTracker(FTracker&& InValue) { Flag = InValue.Flag + 1; always_check(!Flag); } + FTracker& operator=(const FTracker& InValue) { Flag = InValue.Flag - 1; always_check(!Flag); return *this; } + FTracker& operator=(FTracker&& InValue) { Flag = InValue.Flag + 1; always_check(!Flag); return *this; } + }; + + { + TTuple TempA(404, -1); + TTuple TempB(3.14, 1); + TTuple TempC(1.42f, -1); + TTuple<> TempD = { }; + auto TempE = TupleCat(MoveTemp(TempA), TempB, MoveTemp(TempC), MoveTemp(TempD)); + always_check(TempE.GetValue() == 404); + always_check(TempE.GetValue() == 3.14); + always_check(TempE.GetValue() == 1.42f); + always_check((TIsSame>::Value)); + always_check((TIsSame, TTuple, TTuple>::Type>::Value)); + } + + { + always_check(MakeTuple(10, 0.0) == MakeTuple(10.0, 0)); + always_check(MakeTuple(10, 0.0) != MakeTuple(10.1, 0)); + + always_check((MakeTuple(10, 0.0) <=> MakeTuple(10.0, 0)) == 0); + always_check((MakeTuple(10, 1.0) <=> MakeTuple(10.0, 0)) > 0); + always_check((MakeTuple(10, 0.0) <=> MakeTuple(10.1, 0)) < 0); + always_check((MakeTuple(10, 0.0) <=> MakeTuple(10.1, 0)) != 0); + } + + { + double TempB = 0.0; + TTuple TempC(10, TempB); + int16 TempD = 10; + FTracker TempE(0); + TTuple TempF(TempD, MoveTemp(TempE)); + auto TempG = TupleCat(TempC, TempF); + TempG.GetValue<1>() = 3.14; + always_check(TempB == 3.14); + always_check(TempG.GetValue<0>() == 10); + always_check(TempG.GetValue<2>() == 10); + always_check((TIsSame>::Value)); + always_check((TIsSame, TTuple>::Type>::Value)); + } + + { + int32 TempO = 15; + TTuple TempA = { MoveTemp(TempO), 514 }; + + TempA.Apply( + [] (T&& A, U&& B) + { + always_check(A == 15); + always_check(B == 514); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + } + ); + + MoveTemp(TempA).Apply( + [] (T&& A, U&& B) + { + always_check(A == 15); + always_check(B == 514); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + } + ); + + TempA.ApplyAfter( + [] (T&& A, U&& B, V&&C) + { + always_check(A == '-'); + always_check(B == 15); + always_check(C == 514); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + }, + '-' + ); + + MoveTemp(TempA).ApplyAfter( + [] (T&& A, U&& B, V&&C) + { + always_check(A == '-'); + always_check(B == 15); + always_check(C == 514); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + }, + '-' + ); + + TempA.ApplyBefore( + [] (T&& A, U&& B, V&&C) + { + always_check(A == 15); + always_check(B == 514); + always_check(C == '-'); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + }, + '-' + ); + + MoveTemp(TempA).ApplyBefore( + [] (T&& A, U&& B, V&&C) + { + always_check(A == 15); + always_check(B == 514); + always_check(C == '-'); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + always_check((TIsSame::Value)); + }, + '-' + ); + } + + { + TTuple TempA = { 1, 'A' }; + TTuple TempB = TempA.Transform([](auto&& InValue) { return InValue + 1; }); + + VisitTuple( + [] (T&& A) + { + if constexpr (TIsSame::Value) always_check(A == 2); + else if constexpr (TIsSame::Value) always_check(A == 'B'); + else always_check_no_entry(); + }, + TempB + ); + + VisitTuple([](auto&& A) { A++; }, TempB); + + VisitTuple( + [] (T&& A) + { + if constexpr (TIsSame::Value) always_check(A == 3); + else if constexpr (TIsSame::Value) always_check(A == 'C'); + else always_check_no_entry(); + }, + TempB + ); + } + + { + struct FTest + { + FTest(int32 A, float B, char C) + { + always_check(A == 1); + always_check(B == 1.2f); + always_check(C == 'A'); + } + }; + MakeTuple(1, 1.2f, 'A').Construct(); + } + + { + auto Func = [] { return MakeTuple(1, 2.3, 'A'); }; + auto [A, B, C] = Func(); + always_check(A == 1); + always_check(B == 2.3); + always_check(C == 'A'); + always_check((TIsSame::Value)); + } +} + NAMESPACE_UNNAMED_BEGIN template diff --git a/Redcraft.Utility/Source/Public/Templates/Templates.h b/Redcraft.Utility/Source/Public/Templates/Templates.h index d18d47c..3dc5b6f 100644 --- a/Redcraft.Utility/Source/Public/Templates/Templates.h +++ b/Redcraft.Utility/Source/Public/Templates/Templates.h @@ -10,3 +10,4 @@ #include "Templates/Variant.h" #include "Templates/Any.h" #include "Templates/IntegerSequence.h" +#include "Templates/Tuple.h" diff --git a/Redcraft.Utility/Source/Public/Templates/Tuple.h b/Redcraft.Utility/Source/Public/Templates/Tuple.h new file mode 100644 index 0000000..7e32c3a --- /dev/null +++ b/Redcraft.Utility/Source/Public/Templates/Tuple.h @@ -0,0 +1,621 @@ +#pragma once + +#include "CoreTypes.h" +#include "Templates/Utility.h" +#include "TypeTraits/TypeTraits.h" +#include "Templates/IntegerSequence.h" +#include "Templates/ReferenceWrapper.h" +#include "Templates/Invoke.h" + +#include + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +#define RS_TUPLE_ELEMENT_STATIC_ALIAS 1 + +template +struct TTuple; + +NAMESPACE_PRIVATE_BEGIN + +struct FForwardingConstructor { explicit FForwardingConstructor() = default; }; +struct FOtherTupleConstructor { explicit FOtherTupleConstructor() = default; }; + +inline constexpr FForwardingConstructor ForwardingConstructor{ }; +inline constexpr FOtherTupleConstructor OtherTupleConstructor{ }; + +template +struct TTupleElementIndex; + +template +struct TTupleElementIndex + : TConstant::Value ? 0 : (TTupleElementIndex::Value == INDEX_NONE + ? INDEX_NONE : TTupleElementIndex::Value + 1)> +{ }; + +template +struct TTupleElementIndex : TConstant { }; + +template +struct TTupleElementType; + +template +struct TTupleElementType +{ + static_assert(I < sizeof...(Types) + 1, "Tuple type index is invalid"); + using Type = TTupleElementType::Type; +}; + +template +struct TTupleElementType<0, T, Types...> { using Type = T; }; + +template <> +struct TTupleElementType<0> { }; + +template +struct TTupleElement +{ + using ValueType = T; + ValueType Value; + + template + constexpr TTupleElement(Type&& Arg) + : Value(Forward(Arg)) + { } + + TTupleElement() = default; + TTupleElement(TTupleElement&&) = default; + TTupleElement(const TTupleElement&) = default; + TTupleElement& operator=(TTupleElement&&) = default; + TTupleElement& operator=(const TTupleElement&) = default; + + constexpr T& GetValue() & { return static_cast< T& >(Value); } + constexpr const T& GetValue() const & { return static_cast(Value); } + constexpr volatile T& GetValue() volatile& { return static_cast< volatile T& >(Value); } + constexpr const volatile T& GetValue() const volatile& { return static_cast(Value); } + constexpr T&& GetValue() && { return static_cast< T&&>(Value); } + constexpr const T&& GetValue() const && { return static_cast(Value); } + constexpr volatile T&& GetValue() volatile&& { return static_cast< volatile T&&>(Value); } + constexpr const volatile T&& GetValue() const volatile&& { return static_cast(Value); } +}; + +#if RS_TUPLE_ELEMENT_STATIC_ALIAS + +#define DEFINE_TTupleElement(Index, Name) \ + template \ + struct TTupleElement \ + { \ + using Name##Type = T; \ + Name##Type Name; \ + \ + template \ + constexpr TTupleElement(Type&& Arg) \ + : Name(Forward(Arg)) \ + { } \ + \ + TTupleElement() = default; \ + TTupleElement(TTupleElement&&) = default; \ + TTupleElement(const TTupleElement&) = default; \ + TTupleElement& operator=(TTupleElement&&) = default; \ + TTupleElement& operator=(const TTupleElement&) = default; \ + \ + constexpr T& GetValue() & { return static_cast< T& >(Name); } \ + constexpr const T& GetValue() const & { return static_cast(Name); } \ + constexpr volatile T& GetValue() volatile& { return static_cast< volatile T& >(Name); } \ + constexpr const volatile T& GetValue() const volatile& { return static_cast(Name); } \ + constexpr T&& GetValue() && { return static_cast< T&&>(Name); } \ + constexpr const T&& GetValue() const && { return static_cast(Name); } \ + constexpr volatile T&& GetValue() volatile&& { return static_cast< volatile T&&>(Name); } \ + constexpr const volatile T&& GetValue() const volatile&& { return static_cast(Name); } \ + } + +DEFINE_TTupleElement(0x0, First); +DEFINE_TTupleElement(0x1, Second); +DEFINE_TTupleElement(0x2, Third); +DEFINE_TTupleElement(0x3, Fourth); +DEFINE_TTupleElement(0x4, Fifth); +DEFINE_TTupleElement(0x5, Sixth); +DEFINE_TTupleElement(0x6, Seventh); +DEFINE_TTupleElement(0x7, Eighth); +DEFINE_TTupleElement(0x8, Ninth); +DEFINE_TTupleElement(0x9, Tenth); +DEFINE_TTupleElement(0xA, Eleventh); +DEFINE_TTupleElement(0xB, Twelfth); +DEFINE_TTupleElement(0xC, Thirteenth); +DEFINE_TTupleElement(0xD, Fourteenth); +DEFINE_TTupleElement(0xE, Fifteenth); +DEFINE_TTupleElement(0xF, Sixteenth); + +#undef DEFINE_TTupleElement + +#endif + +template +constexpr TTuple::Type...> MakeTupleImpl(Types&&... Args) +{ + return TTuple::Type...>(Forward(Args)...); +} + +template +struct TTupleImpl; + +template +struct TTupleImpl, Types...> : TTupleElement... +{ +protected: + + static constexpr size_t ElementSize = sizeof...(Types); + + template struct TElementType : NAMESPACE_PRIVATE::TTupleElementType { }; + template struct TElementIndex : NAMESPACE_PRIVATE::TTupleElementIndex { }; + + TTupleImpl() = default; + + template + explicit TTupleImpl(FForwardingConstructor, ArgTypes&&... Args) + : TTupleElement(Forward(Args))... + { } + + template + explicit TTupleImpl(FOtherTupleConstructor, TupleType&& InValue) + : TTupleElement(Forward(InValue).template GetValue())... + { } + + TTupleImpl(const TTupleImpl&) = default; + TTupleImpl(TTupleImpl&&) = default; + + template + static constexpr void Assign(LHSTupleType& LHS, RHSTupleType&& RHS) + { + static_assert(ElementSize == LHS.ElementSize && LHS.ElementSize == RHS.ElementSize, "Cannot assign tuple from different size"); + ((LHS.template GetValue() = Forward(RHS).template GetValue()), ...); + } + + TTupleImpl& operator=(const TTupleImpl&) = default; + TTupleImpl& operator=(TTupleImpl&&) = default; + + template + static constexpr auto Apply(F&& Func, TTupleType&& Arg) + { + return Invoke(Forward(Func), Forward(Arg).template GetValue()...); + } + + template + static constexpr auto ApplyAfter(F&& Func, TTupleType&& Arg, ArgTypes&&... OtherArgs) + { + return Invoke(Forward(Func), Forward(OtherArgs)..., Forward(Arg).template GetValue()...); + } + + template + static constexpr auto ApplyBefore(F&& Func, TTupleType&& Arg, ArgTypes&&... OtherArgs) + { + return Invoke(Forward(Func), Forward(Arg).template GetValue()..., Forward(OtherArgs)...); + } + + template + static constexpr auto Transform(F&& Func, TTupleType&& Arg) + { + return MakeTupleImpl(Invoke(Forward(Func), Forward(Arg).template GetValue())...); + } + + template + static constexpr T Construct(TTupleType&& Arg) + { + return T(Forward(Arg).template GetValue()...); + } + +}; + +NAMESPACE_PRIVATE_END + +template +struct TTuple : NAMESPACE_PRIVATE::TTupleImpl, Types...> +{ +private: + + using Super = NAMESPACE_PRIVATE::TTupleImpl, Types...>; + +public: + + static constexpr size_t ElementSize = Super::ElementSize; + + template struct TElementType : Super::template TElementType { }; + template struct TElementIndex : Super::template TElementIndex { }; + + TTuple() = default; + + template requires (ElementSize > 0) && (sizeof...(ArgTypes) == ElementSize) + && (true && ... && TIsConstructible::Value) + && (true && ... && TIsConvertible::Value) + constexpr TTuple(ArgTypes&&... Args) + : Super(NAMESPACE_PRIVATE::ForwardingConstructor, Forward(Args)...) + { } + + template requires (ElementSize > 0) && (sizeof...(ArgTypes) == ElementSize) + && (true && ... && TIsConstructible::Value) + && (!(true && ... && TIsConvertible::Value)) + constexpr explicit TTuple(ArgTypes&&... Args) + : Super(NAMESPACE_PRIVATE::ForwardingConstructor, Forward(Args)...) + { } + + template requires (sizeof...(OtherTypes) == ElementSize) + && (true && ... && TIsConstructible::Value) + && ((ElementSize != 1) || !(TIsConvertible&, typename TElementType<0>::Type>::Value + || TIsConstructible::Type, const TTuple&>::Value + || TIsSame::Type, typename TTuple::template TElementType<0>::Type>::Value)) + && (true && ... && TIsConvertible::Value) + constexpr TTuple(const TTuple& InValue) + : Super(NAMESPACE_PRIVATE::OtherTupleConstructor, InValue) + { } + + template requires (sizeof...(OtherTypes) == ElementSize) + && (true && ... && TIsConstructible::Value) + && ((ElementSize != 1) || !(TIsConvertible&, typename TElementType<0>::Type>::Value + || TIsConstructible::Type, const TTuple&>::Value + || TIsSame::Type, typename TTuple::template TElementType<0>::Type>::Value)) + && (!(true && ... && TIsConvertible::Value)) + constexpr explicit TTuple(const TTuple& InValue) + : Super(NAMESPACE_PRIVATE::OtherTupleConstructor, InValue) + { } + + template requires (sizeof...(OtherTypes) == ElementSize) + && (true && ... && TIsConstructible::Value) + && ((ElementSize != 1) || !(TIsConvertible&&, typename TElementType<0>::Type>::Value + || TIsConstructible::Type, TTuple&&>::Value + || TIsSame::Type, typename TTuple::template TElementType<0>::Type>::Value)) + && (true && ... && TIsConvertible::Value) + constexpr TTuple(TTuple&& InValue) + : Super(NAMESPACE_PRIVATE::OtherTupleConstructor, MoveTemp(InValue)) + { } + + template requires (sizeof...(OtherTypes) == ElementSize) + && (true && ... && TIsConstructible::Value) + && ((ElementSize != 1) || !(TIsConvertible&&, typename TElementType<0>::Type>::Value + || TIsConstructible::Type, TTuple&&>::Value + || TIsSame::Type, typename TTuple::template TElementType<0>::Type>::Value)) + && (!(true && ... && TIsConvertible::Value)) + constexpr explicit TTuple(TTuple&& InValue) + : Super(NAMESPACE_PRIVATE::OtherTupleConstructor, MoveTemp(InValue)) + { } + + TTuple(const TTuple&) = default; + TTuple(TTuple&&) = default; + + template requires (sizeof...(OtherTypes) == ElementSize) + && (true && ... && TIsAssignable::Value) + constexpr TTuple& operator=(const TTuple& InValue) + { + Super::Assign(*this, InValue); + return *this; + } + + template requires (sizeof...(OtherTypes) == ElementSize) + && (true && ... && TIsAssignable::Value) + constexpr TTuple& operator=(TTuple&& InValue) + { + Super::Assign(*this, MoveTemp(InValue)); + return *this; + } + + TTuple& operator=(const TTuple&) = default; + TTuple& operator=(TTuple&&) = default; + + constexpr void* GetData() { return this; } + constexpr const void* GetData() const { return this; } + + template requires (I < ElementSize) constexpr TElementType::Type& GetValue() & { return static_cast< NAMESPACE_PRIVATE::TTupleElement::Type, I>& >(*this).GetValue(); } + template requires (I < ElementSize) constexpr const TElementType::Type& GetValue() const & { return static_cast::Type, I>& >(*this).GetValue(); } + template requires (I < ElementSize) constexpr volatile TElementType::Type& GetValue() volatile& { return static_cast< volatile NAMESPACE_PRIVATE::TTupleElement::Type, I>& >(*this).GetValue(); } + template requires (I < ElementSize) constexpr const volatile TElementType::Type& GetValue() const volatile& { return static_cast::Type, I>& >(*this).GetValue(); } + template requires (I < ElementSize) constexpr TElementType::Type&& GetValue() && { return static_cast< NAMESPACE_PRIVATE::TTupleElement::Type, I>&&>(*this).GetValue(); } + template requires (I < ElementSize) constexpr const TElementType::Type&& GetValue() const && { return static_cast::Type, I>&&>(*this).GetValue(); } + template requires (I < ElementSize) constexpr volatile TElementType::Type&& GetValue() volatile&& { return static_cast< volatile NAMESPACE_PRIVATE::TTupleElement::Type, I>&&>(*this).GetValue(); } + template requires (I < ElementSize) constexpr const volatile TElementType::Type&& GetValue() const volatile&& { return static_cast::Type, I>&&>(*this).GetValue(); } + + template requires (TElementIndex::Value != INDEX_NONE) constexpr T& GetValue() & { return static_cast< TTuple& >(*this).GetValue::Value>(); } + template requires (TElementIndex::Value != INDEX_NONE) constexpr const T& GetValue() const & { return static_cast(*this).GetValue::Value>(); } + template requires (TElementIndex::Value != INDEX_NONE) constexpr volatile T& GetValue() volatile& { return static_cast< volatile TTuple& >(*this).GetValue::Value>(); } + template requires (TElementIndex::Value != INDEX_NONE) constexpr const volatile T& GetValue() const volatile& { return static_cast(*this).GetValue::Value>(); } + template requires (TElementIndex::Value != INDEX_NONE) constexpr T&& GetValue() && { return static_cast< TTuple&&>(*this).GetValue::Value>(); } + template requires (TElementIndex::Value != INDEX_NONE) constexpr const T&& GetValue() const && { return static_cast(*this).GetValue::Value>(); } + template requires (TElementIndex::Value != INDEX_NONE) constexpr volatile T&& GetValue() volatile&& { return static_cast< volatile TTuple&&>(*this).GetValue::Value>(); } + template requires (TElementIndex::Value != INDEX_NONE) constexpr const volatile T&& GetValue() const volatile&& { return static_cast(*this).GetValue::Value>(); } + + template requires TIsInvocable::Value constexpr auto Apply(F&& Func) & { return Super::Apply(Forward(Func), static_cast< TTuple& >(*this)); } + template requires TIsInvocable::Value constexpr auto Apply(F&& Func) const & { return Super::Apply(Forward(Func), static_cast(*this)); } + template requires TIsInvocable::Value constexpr auto Apply(F&& Func) volatile& { return Super::Apply(Forward(Func), static_cast< volatile TTuple& >(*this)); } + template requires TIsInvocable::Value constexpr auto Apply(F&& Func) const volatile& { return Super::Apply(Forward(Func), static_cast(*this)); } + template requires TIsInvocable::Value constexpr auto Apply(F&& Func) && { return Super::Apply(Forward(Func), static_cast< TTuple&&>(*this)); } + template requires TIsInvocable::Value constexpr auto Apply(F&& Func) const && { return Super::Apply(Forward(Func), static_cast(*this)); } + template requires TIsInvocable::Value constexpr auto Apply(F&& Func) volatile&& { return Super::Apply(Forward(Func), static_cast< volatile TTuple&&>(*this)); } + template requires TIsInvocable::Value constexpr auto Apply(F&& Func) const volatile&& { return Super::Apply(Forward(Func), static_cast(*this)); } + + template requires TIsInvocable::Value constexpr auto ApplyAfter(F&& Func, ArgTypes&&... Args) & { return Super::ApplyAfter(Forward(Func), static_cast< TTuple& >(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyAfter(F&& Func, ArgTypes&&... Args) const & { return Super::ApplyAfter(Forward(Func), static_cast(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyAfter(F&& Func, ArgTypes&&... Args) volatile& { return Super::ApplyAfter(Forward(Func), static_cast< volatile TTuple& >(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyAfter(F&& Func, ArgTypes&&... Args) const volatile& { return Super::ApplyAfter(Forward(Func), static_cast(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyAfter(F&& Func, ArgTypes&&... Args) && { return Super::ApplyAfter(Forward(Func), static_cast< TTuple&&>(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyAfter(F&& Func, ArgTypes&&... Args) const && { return Super::ApplyAfter(Forward(Func), static_cast(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyAfter(F&& Func, ArgTypes&&... Args) volatile&& { return Super::ApplyAfter(Forward(Func), static_cast< volatile TTuple&&>(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyAfter(F&& Func, ArgTypes&&... Args) const volatile&& { return Super::ApplyAfter(Forward(Func), static_cast(*this), Forward(Args)...); } + + template requires TIsInvocable::Value constexpr auto ApplyBefore(F&& Func, ArgTypes&&... Args) & { return Super::ApplyBefore(Forward(Func), static_cast< TTuple& >(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyBefore(F&& Func, ArgTypes&&... Args) const & { return Super::ApplyBefore(Forward(Func), static_cast(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyBefore(F&& Func, ArgTypes&&... Args) volatile& { return Super::ApplyBefore(Forward(Func), static_cast< volatile TTuple& >(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyBefore(F&& Func, ArgTypes&&... Args) const volatile& { return Super::ApplyBefore(Forward(Func), static_cast(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyBefore(F&& Func, ArgTypes&&... Args) && { return Super::ApplyBefore(Forward(Func), static_cast< TTuple&&>(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyBefore(F&& Func, ArgTypes&&... Args) const && { return Super::ApplyBefore(Forward(Func), static_cast(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyBefore(F&& Func, ArgTypes&&... Args) volatile&& { return Super::ApplyBefore(Forward(Func), static_cast< volatile TTuple&&>(*this), Forward(Args)...); } + template requires TIsInvocable::Value constexpr auto ApplyBefore(F&& Func, ArgTypes&&... Args) const volatile&& { return Super::ApplyBefore(Forward(Func), static_cast(*this), Forward(Args)...); } + + template requires (true && ... && (TIsInvocable::Value && !TIsSame::Type>::Value)) constexpr auto Transform(F&& Func) & { return Super::Transform(Forward(Func), static_cast< TTuple& >(*this)); } + template requires (true && ... && (TIsInvocable::Value && !TIsSame::Type>::Value)) constexpr auto Transform(F&& Func) const & { return Super::Transform(Forward(Func), static_cast(*this)); } + template requires (true && ... && (TIsInvocable::Value && !TIsSame::Type>::Value)) constexpr auto Transform(F&& Func) volatile& { return Super::Transform(Forward(Func), static_cast< volatile TTuple& >(*this)); } + template requires (true && ... && (TIsInvocable::Value && !TIsSame::Type>::Value)) constexpr auto Transform(F&& Func) const volatile& { return Super::Transform(Forward(Func), static_cast(*this)); } + template requires (true && ... && (TIsInvocable::Value && !TIsSame::Type>::Value)) constexpr auto Transform(F&& Func) && { return Super::Transform(Forward(Func), static_cast< TTuple&&>(*this)); } + template requires (true && ... && (TIsInvocable::Value && !TIsSame::Type>::Value)) constexpr auto Transform(F&& Func) const && { return Super::Transform(Forward(Func), static_cast(*this)); } + template requires (true && ... && (TIsInvocable::Value && !TIsSame::Type>::Value)) constexpr auto Transform(F&& Func) volatile&& { return Super::Transform(Forward(Func), static_cast< volatile TTuple&&>(*this)); } + template requires (true && ... && (TIsInvocable::Value && !TIsSame::Type>::Value)) constexpr auto Transform(F&& Func) const volatile&& { return Super::Transform(Forward(Func), static_cast(*this)); } + + template requires TIsConstructible::Value constexpr T Construct() & { return Super::template Construct(static_cast< TTuple& >(*this)); } + template requires TIsConstructible::Value constexpr T Construct() const & { return Super::template Construct(static_cast(*this)); } + template requires TIsConstructible::Value constexpr T Construct() volatile& { return Super::template Construct(static_cast< volatile TTuple& >(*this)); } + template requires TIsConstructible::Value constexpr T Construct() const volatile& { return Super::template Construct(static_cast(*this)); } + template requires TIsConstructible::Value constexpr T Construct() && { return Super::template Construct(static_cast< TTuple&&>(*this)); } + template requires TIsConstructible::Value constexpr T Construct() const && { return Super::template Construct(static_cast(*this)); } + template requires TIsConstructible::Value constexpr T Construct() volatile&& { return Super::template Construct(static_cast< volatile TTuple&&>(*this)); } + template requires TIsConstructible::Value constexpr T Construct() const volatile&& { return Super::template Construct(static_cast(*this)); } + +}; + +template +TTuple(Types...) -> TTuple; + +template +using TPair = TTuple; + +template struct TIsTupleSpecialization : FFalse { }; +template struct TIsTupleSpecialization> : FTrue { }; + +template requires TIsTupleSpecialization::Type>::Value +struct TTupleElementSize : TConstant::Type::ElementSize> { }; + +template requires TIsTupleSpecialization::Type>::Value +struct TTupleElementType { using Type = typename TCopyCVRef::Type, typename TRemoveCVRef::Type::template TElementType::Type>::Type; }; + +template requires TIsTupleSpecialization::Type>::Value +struct TTupleElementIndex : TupleType::template TElementIndex { }; + +template +constexpr TTuple::Type...> MakeTuple(Types&&... Args) +{ + return TTuple::Type...>(Forward(Args)...); +} + +template +constexpr TTuple Tie(Types&... Args) +{ + return TTuple(Args...); +} + +template +constexpr TTuple ForwardAsTuple(Types&&... Args) +{ + return TTuple(Forward(Args)...); +} + +NAMESPACE_PRIVATE_BEGIN + +struct FTupleEndFlag { }; + +template +struct TTupleCatResultImpl; + +template +struct TTupleCatResultImpl, TTupleTypes...> +{ + using Type = typename TTupleCatResultImpl::Type; +}; + +template +struct TTupleCatResultImpl +{ + using Type = TTuple; +}; + +template +struct TTupleCatMake; + +template +struct TTupleCatMake, TIndexSequence> +{ + template + struct TForward { using Type = typename TConditional::Value, typename TRemoveReference::Type&&, U>::Type; }; + + template + static constexpr TTuple F(TTupleType&& InValue) + { + return TTuple + ( + static_cast(InValue).template GetValue())>::Type> + ( + Forward(InValue).template GetValue() + )... + ); + } +}; + +template +struct TTupleCatForward; + +template +struct TTupleCatForward, TIndexSequence> +{ + template + static constexpr auto F(ForwardType&& ForwardTuple, TTupleType&& InValue) + { + return ForwardAsTuple(Forward(ForwardTuple).template GetValue()..., Forward(InValue).template GetValue()...); + } +}; + +template +struct TTupleCatImpl +{ + template + static constexpr auto F(ForwardType&& ForwardTuple, TTupleType&& InValue, OtherTTupleTypes&&... OtherValue) + { + return F(TTupleCatForward::Value>, TMakeIndexSequence::Value>>::F(Forward(ForwardTuple), Forward(InValue)), Forward(OtherValue)...); + } + + template + static constexpr auto F(ForwardType&& ForwardTuple) + { + return TTupleCatMake::Value>>::F(Forward(ForwardTuple)); + } +}; + +template +struct TTupleEqual; + +template +struct TTupleEqual> +{ + template + static constexpr bool F(const LHSTupleType& LHS, const RHSTupleType& RHS) + { + return (true && ... && (LHS.template GetValue() == RHS.template GetValue())); + } +}; + +template +struct TTupleThreeWay; + +template +struct TTupleThreeWay> +{ + template + static constexpr R F(const LHSTupleType& LHS, const RHSTupleType& RHS) + { + auto Result = TSynthThreeWay{}(LHS.template GetValue(), RHS.template GetValue()); + if (Result != 0) return Result; + return TTupleThreeWay>::F(LHS, RHS); + } +}; + +template +struct TTupleThreeWay> +{ + template + static constexpr R F(const LHSTupleType& LHS, const RHSTupleType& RHS) + { + return R::equivalent; + } +}; + +template +struct TTupleSwapImpl; + +template +struct TTupleSwapImpl> +{ + template + static constexpr void F(TTupleType& A, TTupleType& B) + { + ((Swap(A.template GetValue(), B.template GetValue())), ...); + } +}; + +template +struct TTupleVisitImpl; + +template +struct TTupleVisitImpl> +{ + template + static constexpr void F(G&& Func, TupleTypes&&... Tuples) + { + Invoke(Forward(Func), Forward(Tuples).template GetValue()...); + TTupleVisitImpl>::F(Forward(Func), Forward(Tuples)...); + } +}; + +template <> +struct TTupleVisitImpl> +{ + template + static constexpr void F(TupleTypes&&... Tuples) { } +}; + +NAMESPACE_PRIVATE_END + +template requires (true && ... && (TIsTupleSpecialization::Type>::Value)) +struct TTupleCatResult { using Type = typename NAMESPACE_PRIVATE::TTupleCatResultImpl::Type..., NAMESPACE_PRIVATE::FTupleEndFlag>::Type; }; + +template requires (true && ... && (TIsTupleSpecialization::Type>::Value)) +constexpr auto TupleCat(TTupleTypes&&... Args) +{ + using R = typename TTupleCatResult::Type; + if constexpr (sizeof...(Args) == 0) return R(); + else return NAMESPACE_PRIVATE::TTupleCatImpl::F(Forward(Args)...); +} + +template requires ((sizeof...(LHSTypes) != sizeof...(RHSTypes)) || (true && ... && CWeaklyEqualityComparableWith)) +constexpr bool operator==(const TTuple& LHS, const TTuple& RHS) +{ + if constexpr (sizeof...(LHSTypes) != sizeof...(RHSTypes)) return false; + return NAMESPACE_PRIVATE::TTupleEqual>::F(LHS, RHS); +} + +template requires ((sizeof...(LHSTypes) == sizeof...(RHSTypes)) && (true && ... && (CSynthThreeWayComparableWith))) +constexpr typename TCommonComparisonCategory::Type...>::Type operator<=>(const TTuple& LHS, const TTuple& RHS) +{ + using R = typename TCommonComparisonCategory::Type...>::Type; + return NAMESPACE_PRIVATE::TTupleThreeWay>::F(LHS, RHS); +} + +template requires (true && ... && (TIsMoveConstructible::Value && TIsSwappable::Value)) +constexpr void Swap(TTuple& A, TTuple& B) +{ + NAMESPACE_PRIVATE::TTupleSwapImpl>::F(A, B); +} + +template requires TIsInvocable::Value +constexpr void VisitTuple(F&& Func) { } + +template +constexpr void VisitTuple(F&& Func, FirstTupleType&& FirstTuple, TupleTypes&&... Tuples) +{ + NAMESPACE_PRIVATE::TTupleVisitImpl::Value>>::F(Forward(Func), Forward(FirstTuple), Forward(Tuples)...); +} + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END + +NAMESPACE_STD_BEGIN + +// Support structure binding, should not be directly used +template struct tuple_size> : integral_constant>::Value> { }; +template struct tuple_element> { using type = typename NAMESPACE_REDCRAFT::TTupleElementType>::Type; }; + +NAMESPACE_STD_END + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +// Support structure binding, should not be directly used +template constexpr typename TTupleElementType>::Type& get( NAMESPACE_REDCRAFT::TTuple& InValue) { return static_cast< typename TTupleElementType>::Type& >(InValue.template GetValue()); } +template constexpr const typename TTupleElementType>::Type& get(const NAMESPACE_REDCRAFT::TTuple& InValue) { return static_cast>::Type& >(InValue.template GetValue()); } +template constexpr volatile typename TTupleElementType>::Type& get( volatile NAMESPACE_REDCRAFT::TTuple& InValue) { return static_cast< volatile typename TTupleElementType>::Type& >(InValue.template GetValue()); } +template constexpr const volatile typename TTupleElementType>::Type& get(const volatile NAMESPACE_REDCRAFT::TTuple& InValue) { return static_cast>::Type& >(InValue.template GetValue()); } +template constexpr typename TTupleElementType>::Type&& get( NAMESPACE_REDCRAFT::TTuple&& InValue) { return static_cast< typename TTupleElementType>::Type&&>(InValue.template GetValue()); } +template constexpr const typename TTupleElementType>::Type&& get(const NAMESPACE_REDCRAFT::TTuple&& InValue) { return static_cast>::Type&&>(InValue.template GetValue()); } +template constexpr volatile typename TTupleElementType>::Type&& get( volatile NAMESPACE_REDCRAFT::TTuple&& InValue) { return static_cast< volatile typename TTupleElementType>::Type&&>(InValue.template GetValue()); } +template constexpr const volatile typename TTupleElementType>::Type&& get(const volatile NAMESPACE_REDCRAFT::TTuple&& InValue) { return static_cast>::Type&&>(InValue.template GetValue()); } + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Templates/Utility.h b/Redcraft.Utility/Source/Public/Templates/Utility.h index d5c7e6b..e0c0ae6 100644 --- a/Redcraft.Utility/Source/Public/Templates/Utility.h +++ b/Redcraft.Utility/Source/Public/Templates/Utility.h @@ -92,6 +92,14 @@ constexpr T* AddressOf(T& Object) return &Object; } +struct FIgnore +{ + template + constexpr void operator=(T&&) const { } +}; + +inline constexpr FIgnore Ignore; + NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h b/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h index dd0ffba..a39cf32 100644 --- a/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h +++ b/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h @@ -14,6 +14,7 @@ REDCRAFTUTILITY_API void TestReferenceWrapper(); REDCRAFTUTILITY_API void TestOptional(); REDCRAFTUTILITY_API void TestVariant(); REDCRAFTUTILITY_API void TestAny(); +REDCRAFTUTILITY_API void TestTuple(); REDCRAFTUTILITY_API void TestMiscTemplates(); NAMESPACE_END(Testing)