From 3ed3ec5258372c6ba3d65ab1255b02e7048d6c29 Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Sat, 2 Nov 2024 23:18:13 +0800 Subject: [PATCH] refactor(string): refactor TString to inherit from TArray and without null-termination --- .../Source/Private/Testing/StringTesting.cpp | 26 +- .../Source/Public/String/String.h | 408 ++++++++---------- 2 files changed, 204 insertions(+), 230 deletions(-) diff --git a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp index 03cd141..8621392 100644 --- a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp @@ -332,7 +332,7 @@ void TestTemplateString() TString Empty; always_check(Empty.IsEmpty()); - always_check(TStringView(Empty.ToCString()) == LITERAL(T, "")); + always_check(TStringView(*Empty) == LITERAL(T, "")); TString StrA(32, LITERAL(T, 'A')); @@ -341,11 +341,11 @@ void TestTemplateString() TString StrD(TStringView(LITERAL(T, "ABCDEFG"))); TString StrE({ LITERAL(T, 'A'), LITERAL(T, 'B'), LITERAL(T, 'C') }); - always_check(TStringView(StrA.ToCString()) == LITERAL(T, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")); - always_check(TStringView(StrB.ToCString()) == LITERAL(T, "ABC")); - always_check(TStringView(StrC.ToCString()) == LITERAL(T, "ABCDEFG")); - always_check(TStringView(StrD.ToCString()) == LITERAL(T, "ABCDEFG")); - always_check(TStringView(StrE.ToCString()) == LITERAL(T, "ABC")); + always_check(TStringView(*StrA) == LITERAL(T, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")); + always_check(TStringView(*StrB) == LITERAL(T, "ABC")); + always_check(TStringView(*StrC) == LITERAL(T, "ABCDEFG")); + always_check(TStringView(*StrD) == LITERAL(T, "ABCDEFG")); + always_check(TStringView(*StrE) == LITERAL(T, "ABC")); TString StrI(StrC); TString StrII(MoveTemp(StrC)); @@ -356,18 +356,18 @@ void TestTemplateString() StrIII = StrD; StrIV = MoveTemp(StrD); - always_check(TStringView(StrI .ToCString()) == LITERAL(T, "ABCDEFG")); - always_check(TStringView(StrII .ToCString()) == LITERAL(T, "ABCDEFG")); - always_check(TStringView(StrIII.ToCString()) == LITERAL(T, "ABCDEFG")); - always_check(TStringView(StrIV .ToCString()) == LITERAL(T, "ABCDEFG")); + always_check(TStringView(StrI ) == LITERAL(T, "ABCDEFG")); + always_check(TStringView(StrII ) == LITERAL(T, "ABCDEFG")); + always_check(TStringView(StrIII) == LITERAL(T, "ABCDEFG")); + always_check(TStringView(StrIV ) == LITERAL(T, "ABCDEFG")); - always_check(TStringView(StrC.ToCString()) == LITERAL(T, "")); - always_check(TStringView(StrD.ToCString()) == LITERAL(T, "")); + always_check(StrC == LITERAL(T, "")); + always_check(StrD == LITERAL(T, "")); StrA.Reset(); always_check(StrA.IsEmpty()); - always_check(TStringView(StrA.ToCString()) == LITERAL(T, "")); + always_check(StrA == LITERAL(T, "")); } { diff --git a/Redcraft.Utility/Source/Public/String/String.h b/Redcraft.Utility/Source/Public/String/String.h index f0ddfbf..7747dd4 100644 --- a/Redcraft.Utility/Source/Public/String/String.h +++ b/Redcraft.Utility/Source/Public/String/String.h @@ -5,47 +5,93 @@ #include "Containers/Array.h" #include "String/StringView.h" #include "Templates/Utility.h" -#include "Templates/TypeHash.h" #include "Templates/Optional.h" #include "Templates/Container.h" #include "Containers/Iterator.h" #include "TypeTraits/TypeTraits.h" -#include "Miscellaneous/Compare.h" -#include "Memory/MemoryOperator.h" +#include "Templates/Noncopyable.h" #include "Miscellaneous/AssertionMacros.h" NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) -template -using TDefaultStringAllocator = TInlineAllocator<(40 - 3 * sizeof(size_t)) / sizeof(T)>; +NAMESPACE_PRIVATE_BEGIN -template Allocator = TDefaultStringAllocator> -class TString +template +class TCStringFromTString final : FNoncopyable { public: - using ElementType = typename TArray::ElementType; - using AllocatorType = typename TArray::AllocatorType; + FORCEINLINE TCStringFromTString(const T* InPtr, bool bInDelete) + : Ptr(InPtr), bDelete(bInDelete) + { } - using Reference = typename TArray:: Reference; - using ConstReference = typename TArray::ConstReference; + FORCEINLINE TCStringFromTString(TCStringFromTString&& InValue) + : Ptr(InValue.Ptr), bDelete(Exchange(InValue.bDelete, false)) + { } - using Iterator = typename TArray:: Iterator; - using ConstIterator = typename TArray::ConstIterator; + FORCEINLINE ~TCStringFromTString() + { + if (bDelete) delete[] Ptr; + } - using ReverseIterator = typename TArray:: ReverseIterator; - using ConstReverseIterator = typename TArray::ConstReverseIterator; + FORCEINLINE TCStringFromTString& operator=(TCStringFromTString&& InValue) + { + if (bDelete) delete[] Ptr; + + Ptr = InValue.Ptr; + + bDelete = Exchange(InValue.bDelete, false); + + return *this; + } + + NODISCARD FORCEINLINE operator const T*() const { return Ptr; } + +private: + + const T* Ptr; + bool bDelete; + +}; + +NAMESPACE_PRIVATE_END + +/** The default string allocator that uses SSO and can be placed right into FAny without dynamically allocating memory. */ +template +using TDefaultStringAllocator = TInlineAllocator<(40 - 3 * sizeof(size_t)) / sizeof(T)>; + +/** A string class that stores and manipulates sequences of characters. Unlike std::basic_string, it is not null-terminated. */ +template Allocator = TDefaultStringAllocator> +class TString : public TArray +{ +private: + + using Super = TArray; + +public: + + using ElementType = typename Super::ElementType; + using AllocatorType = typename Super::AllocatorType; + + using Reference = typename Super:: Reference; + using ConstReference = typename Super::ConstReference; + + using Iterator = typename Super:: Iterator; + using ConstIterator = typename Super::ConstIterator; + + using ReverseIterator = typename Super:: ReverseIterator; + using ConstReverseIterator = typename Super::ConstReverseIterator; static_assert(CContiguousIterator< Iterator>); static_assert(CContiguousIterator); /** Default constructor. Constructs an empty string. */ - FORCEINLINE TString() : NativeData({ LITERAL(ElementType, '\0') }) { } + FORCEINLINE TString() = default; /** Constructs the string with 'Count' copies of characters with 'InValue'. */ - FORCEINLINE TString(size_t Count, ElementType InChar) : TString(MakeCountedConstantIterator(InChar, Count), DefaultSentinel) { } + FORCEINLINE TString(size_t Count, ElementType InChar) : Super(Count, InChar) { } /** Constructs a string with the contents of the range ['InPtr', 'InPtr' + 'Count'). */ FORCEINLINE TString(const ElementType* InPtr, size_t Count) : TString(TStringView(InPtr, Count)) @@ -68,52 +114,22 @@ public: /** Constructs the string with the contents of the range ['First', 'Last'). */ template S> requires (CConstructibleFrom>) - TString(I First, S Last) - { - if constexpr (CForwardIterator) - { - if constexpr (CSizedSentinelFor) { checkf(First <= Last, TEXT("Illegal range iterator. Please check First <= Last.")); } - - const size_t Count = Iteration::Distance(First, Last); - - NativeData.SetNum(Count + 1); - - for (size_t Index = 0; Index != Count; ++Index) - { - NativeData[Index] = ElementType(*First++); - } - - NativeData.Back() = LITERAL(ElementType, '\0'); - } - else - { - while (First != Last) - { - NativeData.PushBack(ElementType(*First)); - ++First; - } - - NativeData.PushBack(LITERAL(ElementType, '\0')); - } - } + FORCEINLINE TString(I First, S Last) : Super(MoveTemp(First), MoveTemp(Last)) { } /** Copy constructor. Constructs the string with the copy of the contents of 'InValue'. */ FORCEINLINE TString(const TString&) = default; /** Move constructor. After the move, 'InValue' is guaranteed to be empty. */ - FORCEINLINE TString(TString&& InValue) : NativeData(MoveTemp(InValue.NativeData)) { InValue.NativeData.PushBack(LITERAL(ElementType, '\0')); } + FORCEINLINE TString(TString&&) = default; /** Constructs the string with the contents of the initializer list. */ FORCEINLINE TString(initializer_list IL) : TString(Iteration::Begin(IL), Iteration::End(IL)) { } - /** Destructs the string. The destructors of the characters are called and the used storage is deallocated. */ - FORCEINLINE ~TString() = default; - /** Copy assignment operator. Replaces the contents with a copy of the contents of 'InValue'. */ FORCEINLINE TString& operator=(const TString&) = default; /** Move assignment operator. After the move, 'InValue' is guaranteed to be empty. */ - FORCEINLINE TString& operator=(TString&& InValue) { NativeData = MoveTemp(InValue.NativeData); InValue.NativeData.PushBack(LITERAL(ElementType, '\0')); return *this; } + FORCEINLINE TString& operator=(TString&&) = default; /** Compares the contents of two strings. */ NODISCARD friend FORCEINLINE bool operator==(const TString& LHS, const TString& RHS) { return TStringView(LHS) == TStringView(RHS); } @@ -136,47 +152,47 @@ public: /** Inserts 'InValue' before 'Index' in the string. */ FORCEINLINE Iterator Insert(size_t Index, ElementType InValue) { - checkf(Index <= Num(), TEXT("Illegal index. Please check Index <= Num().")); + checkf(Index <= this->Num(), TEXT("Illegal index. Please check Index <= Num().")); - return Insert(Begin() + Index, InValue); + return Insert(this->Begin() + Index, InValue); } /** Inserts 'InValue' before 'Iter' in the string. */ FORCEINLINE Iterator Insert(ConstIterator Iter, ElementType InValue) { - checkf(IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); - return NativeData.Insert(Iter, InValue); + return Super::Insert(Iter, InValue); } /** Inserts 'Count' copies of the 'InValue' before 'Index' in the string. */ FORCEINLINE Iterator Insert(size_t Index, size_t Count, ElementType InValue) { - checkf(Index <= Num(), TEXT("Illegal index. Please check Index <= Num().")); + checkf(Index <= this->Num(), TEXT("Illegal index. Please check Index <= Num().")); - return Insert(Begin() + Index, Count, InValue); + return Insert(this->Begin() + Index, Count, InValue); } /** Inserts 'Count' copies of the 'InValue' before 'Iter' in the string. */ FORCEINLINE Iterator Insert(ConstIterator Iter, size_t Count, ElementType InValue) { - checkf(IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); - return NativeData.Insert(Iter, Count, InValue); + return Super::Insert(Iter, Count, InValue); } /** Inserts characters from the 'View' before 'Index' in the string. */ FORCEINLINE Iterator Insert(size_t Index, TStringView View) { - checkf(Index <= Num(), TEXT("Illegal index. Please check Index <= Num().")); + checkf(Index <= this->Num(), TEXT("Illegal index. Please check Index <= Num().")); - return Insert(Begin() + Index, View); + return Insert(this->Begin() + Index, View); } /** Inserts characters from the 'View' before 'Iter' in the string. */ FORCEINLINE Iterator Insert(ConstIterator Iter, TStringView View) { - checkf(IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); return Insert(Iter, View.Begin(), View.End()); } @@ -185,74 +201,71 @@ public: template S> requires (CConstructibleFrom>) FORCEINLINE Iterator Insert(size_t Index, I First, S Last) { - checkf(Index <= Num(), TEXT("Illegal index. Please check Index <= Num().")); + checkf(Index <= this->Num(), TEXT("Illegal index. Please check Index <= Num().")); - return Insert(Begin() + Index, MoveTemp(First), MoveTemp(Last)); + return Insert(this->Begin() + Index, MoveTemp(First), MoveTemp(Last)); } /** Inserts characters from the range ['First', 'Last') before 'Iter'. */ template S> requires (CConstructibleFrom>) FORCEINLINE Iterator Insert(ConstIterator Iter, I First, S Last) { - checkf(IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); - return NativeData.Insert(Iter, MoveTemp(First), MoveTemp(Last)); + return Super::Insert(Iter, MoveTemp(First), MoveTemp(Last)); } /** Inserts characters from the initializer list before 'Index' in the string. */ FORCEINLINE Iterator Insert(size_t Index, initializer_list IL) { - checkf(Index <= Num(), TEXT("Illegal index. Please check Index <= Num().")); + checkf(Index <= this->Num(), TEXT("Illegal index. Please check Index <= Num().")); - return Insert(Begin() + Index, IL); + return Insert(this->Begin() + Index, IL); } /** Inserts characters from the initializer list before 'Iter' in the string. */ FORCEINLINE Iterator Insert(ConstIterator Iter, initializer_list IL) { - checkf(IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(Iter), TEXT("Read access violation. Please check IsValidIterator().")); - return NativeData.Insert(Iter, IL); + return Super::Insert(Iter, IL); } /** Erases the character at 'Index' in the string. But it may change the order of characters. */ FORCEINLINE Iterator Erase(size_t Index, bool bAllowShrinking = true) { - checkf(Index < Num(), TEXT("Illegal index. Please check Index < Num().")); + checkf(Index < this->Num(), TEXT("Illegal index. Please check Index < Num().")); - return Erase(Begin() + Index, bAllowShrinking); + return Erase(this->Begin() + Index, bAllowShrinking); } /** Erases the character at 'Iter' in the string. But it may change the order of characters. */ FORCEINLINE Iterator Erase(ConstIterator Iter, bool bAllowShrinking = true) { - checkf(IsValidIterator(Iter) && Iter != End(), TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(Iter) && Iter != this->End(), TEXT("Read access violation. Please check IsValidIterator().")); - return NativeData.StableErase(Iter, bAllowShrinking); + return Super::StableErase(Iter, bAllowShrinking); } /** Erases 'CountToErase' characters starting from 'Index' in the string. But it may change the order of characters. */ FORCEINLINE Iterator Erase(size_t Index, size_t CountToErase, bool bAllowShrinking = true) { - checkf(Index <= Num() && Index + CountToErase <= Num(), TEXT("Illegal substring range. Please check Index and CountToErase.")); + checkf(Index <= this->Num() && Index + CountToErase <= this->Num(), TEXT("Illegal substring range. Please check Index and CountToErase.")); - return Erase(Begin() + Index, Begin() + Index + CountToErase, bAllowShrinking); + return Erase(this->Begin() + Index, this->Begin() + Index + CountToErase, bAllowShrinking); } /** Erases the characters in the range ['First', 'Last') in the string. But it may change the order of characters. */ FORCEINLINE Iterator Erase(ConstIterator First, ConstIterator Last, bool bAllowShrinking = true) { - checkf(IsValidIterator(First) && IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(First) && this->IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); - return NativeData.StableErase(First, Last, bAllowShrinking); + return Super::StableErase(First, Last, bAllowShrinking); } - /** Appends the given character value to the end of the string. */ - FORCEINLINE void PushBack(ElementType InValue) { NativeData.Back() = InValue; NativeData.PushBack(LITERAL(ElementType, '\0')); } - - /** Removes the last character of the string. The string cannot be empty. */ - FORCEINLINE void PopBack(bool bAllowShrinking = true) { NativeData.PopBack(bAllowShrinking); NativeData.Back() = LITERAL(ElementType, '\0'); } + /** Here, the 'Erase' is already stable and there is no need to provide 'StableErase'. */ + void StableErase(...) = delete; /** Appends 'Count' copies of the 'InValue' to the end of the string. */ TString& Append(size_t Count, ElementType InChar) { return Append(MakeCountedConstantIterator(InChar, Count), DefaultSentinel); } @@ -270,22 +283,18 @@ public: const size_t Count = Iteration::Distance(First, Last); - const size_t CurrentNum = Num(); + const size_t CurrentNum = this->Num(); - NativeData.SetNum(CurrentNum + Count + 1); + this->SetNum(CurrentNum + Count); for (size_t Index = CurrentNum; Index != CurrentNum + Count; ++Index) { - NativeData[Index] = ElementType(*First++); + (*this)[Index] = ElementType(*First++); } - - NativeData.Back() = LITERAL(ElementType, '\0'); } else { - NativeData.Insert(NativeData.End() - 1, MoveTemp(First), MoveTemp(Last)); - - NativeData.PushBack(LITERAL(ElementType, '\0')); + Insert(this->End(), MoveTemp(First), MoveTemp(Last)); } return *this; @@ -373,15 +382,15 @@ public: /** Replace the substring [Index, Index + CountToReplace) with 'Count' copies of the 'InChar'. */ FORCEINLINE TString& Replace(size_t Index, size_t CountToReplace, size_t Count, ElementType InChar) { - checkf(Index <= Num() && Index + CountToReplace <= Num(), TEXT("Illegal substring range. Please check Index and CountToReplace.")); + checkf(Index <= this->Num() && Index + CountToReplace <= this->Num(), TEXT("Illegal substring range. Please check Index and CountToReplace.")); - return Replace(Begin() + Index, Begin() + Index + CountToReplace, Count, InChar); + return Replace(this->Begin() + Index, this->Begin() + Index + CountToReplace, Count, InChar); } /** Replace the substring ['First', 'Last') with 'Count' copies of the 'InChar'. */ FORCEINLINE TString& Replace(ConstIterator First, ConstIterator Last, size_t Count, ElementType InChar) { - checkf(IsValidIterator(First) && IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(First) && this->IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); return Replace(First, Last, MakeCountedConstantIterator(InChar, Count), DefaultSentinel); } @@ -389,15 +398,15 @@ public: /** Replace the substring [Index, Index + CountToReplace) with the contents of the 'View'. */ FORCEINLINE TString& Replace(size_t Index, size_t CountToReplace, TStringView View) { - checkf(Index <= Num() && Index + CountToReplace <= Num(), TEXT("Illegal substring range. Please check Index and CountToReplace.")); + checkf(Index <= this->Num() && Index + CountToReplace <= this->Num(), TEXT("Illegal substring range. Please check Index and CountToReplace.")); - return Replace(Begin() + Index, Begin() + Index + CountToReplace, View); + return Replace(this->Begin() + Index, this->Begin() + Index + CountToReplace, View); } /** Replace the substring ['First', 'Last') with the contents of the 'View'. */ FORCEINLINE TString& Replace(ConstIterator First, ConstIterator Last, TStringView View) { - checkf(IsValidIterator(First) && IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(First) && this->IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); return Replace(First, Last, View.Begin(), View.End()); } @@ -406,62 +415,61 @@ public: template S> requires (CConstructibleFrom>) FORCEINLINE TString& Replace(size_t Index, size_t CountToReplace, I InString, S Sentinel) { - checkf(Index <= Num() && Index + CountToReplace <= Num(), TEXT("Illegal substring range. Please check Index and CountToReplace.")); + checkf(Index <= this->Num() && Index + CountToReplace <= this->Num(), TEXT("Illegal substring range. Please check Index and CountToReplace.")); - return Replace(Begin() + Index, Begin() + Index + CountToReplace, MoveTemp(InString), MoveTemp(Sentinel)); + return Replace(this->Begin() + Index, this->Begin() + Index + CountToReplace, MoveTemp(InString), MoveTemp(Sentinel)); } /** Replace the substring ['First', 'Last') with the contents of the range ['InString', 'Sentinel'). */ template S> requires (CConstructibleFrom>) TString& Replace(ConstIterator First, ConstIterator Last, I InString, S Sentinel) { - checkf(IsValidIterator(First) && IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(First) && this->IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); if constexpr (CForwardIterator) { if (CSizedSentinelFor) { checkf(First <= Last, TEXT("Illegal range iterator. Please check First <= Last.")); } - const size_t InsertIndex = First - Begin(); + const size_t InsertIndex = First - this->Begin(); const size_t RemoveCount = Iteration::Distance(First, Last); const size_t InsertCount = Iteration::Distance(InString, Sentinel); - const size_t NumToReset = Num() - RemoveCount + InsertCount; + const size_t NumToReset = this->Num() - RemoveCount + InsertCount; if (InsertCount < RemoveCount) { for (size_t Index = InsertIndex; Index != InsertIndex + InsertCount; ++Index) { - NativeData[Index] = ElementType(*InString++); + (*this)[Index] = ElementType(*InString++); } for (size_t Index = InsertIndex + InsertCount; Index != NumToReset; ++Index) { - NativeData[Index] = NativeData[Index + (RemoveCount - InsertCount)]; + (*this)[Index] = (*this)[Index + (RemoveCount - InsertCount)]; } - NativeData.SetNum(NumToReset + 1); + this->SetNum(NumToReset); } else { - NativeData.SetNum(NumToReset + 1); + this->SetNum(NumToReset); - for (size_t Index = Num(); Index != InsertIndex + InsertCount - 1; --Index) + for (size_t Index = this->Num(); Index != InsertIndex + InsertCount - 1; --Index) { - NativeData[Index - 1] = NativeData[Index + (RemoveCount - InsertCount) - 1]; + (*this)[Index - 1] = (*this)[Index + (RemoveCount - InsertCount) - 1]; } for (size_t Index = InsertIndex; Index != InsertIndex + InsertCount; ++Index) { - NativeData[Index] = ElementType(*InString++); + (*this)[Index] = ElementType(*InString++); } } - - NativeData.Back() = LITERAL(ElementType, '\0'); } else { TString Temp(MoveTemp(First), MoveTemp(Last)); + return Replace(First, Last, Temp.Begin(), Temp.End()); } @@ -471,15 +479,15 @@ public: /** Replace the substring [Index, Index + CountToReplace) with the contents of the initializer list. */ FORCEINLINE TString& Replace(size_t Index, size_t CountToReplace, initializer_list IL) { - checkf(Index <= Num() && Index + CountToReplace <= Num(), TEXT("Illegal substring range. Please check Index and CountToReplace.")); + checkf(Index <= this->Num() && Index + CountToReplace <= this->Num(), TEXT("Illegal substring range. Please check Index and CountToReplace.")); - return Replace(Begin() + Index, Begin() + Index + CountToReplace, IL); + return Replace(this->Begin() + Index, this->Begin() + Index + CountToReplace, IL); } /** Replace the substring ['First', 'Last') with the contents of the initializer list. */ FORCEINLINE TString& Replace(ConstIterator First, ConstIterator Last, initializer_list IL) { - checkf(IsValidIterator(First) && IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); + checkf(this->IsValidIterator(First) && this->IsValidIterator(Last) && First <= Last, TEXT("Read access violation. Please check IsValidIterator().")); return Replace(First, Last, Iteration::Begin(IL), Iteration::End(IL)); } @@ -487,7 +495,7 @@ public: /** Obtains a string that is a view over the 'Count' characters of this string view starting at 'Offset'. */ FORCEINLINE TString Substr(size_t Offset = 0, size_t Count = DynamicExtent) const { - checkf(Offset <= Num() && Offset + Count <= Num(), TEXT("Illegal substring range. Please check Offset and Count.")); + checkf(Offset <= this->Num() && Offset + Count <= this->Num(), TEXT("Illegal substring range. Please check Offset and Count.")); return TStringView(*this).Substr(Offset, Count); } @@ -497,7 +505,7 @@ public: { checkf(Dest != nullptr, TEXT("Illegal destination buffer. Please check the pointer.")); - checkf(Offset <= Num() && (Count == DynamicExtent || Offset + Count <= Num()), TEXT("Illegal subview range. Please check Offset and Count.")); + checkf(Offset <= this->Num() && (Count == DynamicExtent || Offset + Count <= this->Num()), TEXT("Illegal subview range. Please check Offset and Count.")); return TStringView(*this).Copy(Dest, Count, Offset); } @@ -507,7 +515,7 @@ public: /** @return The index of the first occurrence of the given substring, or INDEX_NONE if not found. */ NODISCARD size_t Find(TStringView View, size_t Index = 0) const { - checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).Find(View, Index); } @@ -515,7 +523,7 @@ public: /** @return The index of the first occurrence of the given character, or INDEX_NONE if not found. */ NODISCARD size_t Find(ElementType Char, size_t Index = 0) const { - checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).Find(Char, Index); } @@ -524,7 +532,7 @@ public: template F> NODISCARD size_t Find(F&& InPredicate, size_t Index = 0) const { - checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).Find(Forward(InPredicate), Index); } @@ -532,7 +540,7 @@ public: /** @return The index of the last occurrence of the given substring, or INDEX_NONE if not found. */ NODISCARD size_t RFind(TStringView View, size_t Index = INDEX_NONE) const { - checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index == INDEX_NONE || Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).RFind(View, Index); } @@ -540,7 +548,7 @@ public: /** @return The index of the last occurrence of the given character, or INDEX_NONE if not found. */ NODISCARD size_t RFind(ElementType Char, size_t Index = INDEX_NONE) const { - checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index == INDEX_NONE || Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).RFind(Char, Index); } @@ -549,7 +557,7 @@ public: template F> NODISCARD size_t RFind(F&& InPredicate, size_t Index = INDEX_NONE) const { - checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index == INDEX_NONE || Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).RFind(Forward(InPredicate), Index); } @@ -557,7 +565,7 @@ public: /** @return The index of the first occurrence of the character contained in the given view, or INDEX_NONE if not found. */ NODISCARD FORCEINLINE size_t FindFirstOf(TStringView View, size_t Index = 0) const { - checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).FindFirstOf(View, Index); } @@ -565,7 +573,7 @@ public: /** @return The index of the first occurrence of the given character, or INDEX_NONE if not found. */ NODISCARD FORCEINLINE size_t FindFirstOf(ElementType Char, size_t Index = 0) const { - checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).FindFirstOf(Char, Index); } @@ -573,7 +581,7 @@ public: /** @return The index of the last occurrence of the character contained in the given view, or INDEX_NONE if not found. */ NODISCARD FORCEINLINE size_t FindLastOf(TStringView View, size_t Index = INDEX_NONE) const { - checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index == INDEX_NONE || Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).FindLastOf(View, Index); } @@ -581,7 +589,7 @@ public: /** @return The index of the last occurrence of the given character, or INDEX_NONE if not found. */ NODISCARD FORCEINLINE size_t FindLastOf(ElementType Char, size_t Index = INDEX_NONE) const { - checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index == INDEX_NONE || Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).FindLastOf(Char, Index); } @@ -589,7 +597,7 @@ public: /** @return The index of the first absence of the character contained in the given view, or INDEX_NONE if not found. */ NODISCARD FORCEINLINE size_t FindFirstNotOf(TStringView View, size_t Index = 0) const { - checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).FindFirstNotOf(View, Index); } @@ -597,7 +605,7 @@ public: /** @return The index of the first absence of the given character, or INDEX_NONE if not found. */ NODISCARD FORCEINLINE size_t FindFirstNotOf(ElementType Char, size_t Index = 0) const { - checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).FindFirstNotOf(Char, Index); } @@ -605,7 +613,7 @@ public: /** @return The index of the last absence of the character contained in the given view, or INDEX_NONE if not found. */ NODISCARD FORCEINLINE size_t FindLastNotOf(TStringView View, size_t Index = INDEX_NONE) const { - checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index == INDEX_NONE || Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).FindLastNotOf(View, Index); } @@ -613,7 +621,7 @@ public: /** @return The index of the last absence of the given character, or INDEX_NONE if not found. */ NODISCARD FORCEINLINE size_t FindLastNotOf(ElementType Char, size_t Index = INDEX_NONE) const { - checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + checkf(Index == INDEX_NONE || Index < this->Num(), TEXT("Illegal index. Please check Index.")); return TStringView(*this).FindLastNotOf(Char, Index); } @@ -636,7 +644,7 @@ public: template bool DecodeFrom(TStringView View, bool bAllowShrinking = true) { - NativeData.Reset(false); + this->Reset(false); auto AppendToResult = [this](auto& Self, TStringView View) -> bool { @@ -645,7 +653,7 @@ public: if constexpr (CSameAs && CSameAs || CSameAs && CSameAs) { // Unable to determine whether the user-preferred locale encoded character is valid or not, it is assumed to be valid. - NativeData.Insert(NativeData.End(), View.Begin(), View.End()); + Insert(this->End(), View.Begin(), View.End()); return true; } @@ -684,7 +692,7 @@ public: { for (wchar* Iter = Buffer; Iter != NextTo; ++Iter) { - NativeData.PushBack(*Iter); + this->PushBack(*Iter); } } else @@ -729,7 +737,7 @@ public: for (char* Iter = Buffer; Iter != NextTo; ++Iter) { - NativeData.PushBack(*Iter); + this->PushBack(*Iter); } BeginFrom = NextFrom; @@ -847,25 +855,25 @@ public: if (!(Char & ~0b0000000'00000000'00000000'01111111)) // 0XXXXXXX { - NativeData.PushBack(static_cast(Char)); + this->PushBack(static_cast(Char)); } else if (!(Char & ~0b0000000'00000000'00000111'11111111)) // 110XXXXX 10XXXXXX { - NativeData.PushBack(static_cast(0b11000000 | (Char >> 6 & 0b00011111))); - NativeData.PushBack(static_cast(0b10000000 | (Char & 0b00111111))); + this->PushBack(static_cast(0b11000000 | (Char >> 6 & 0b00011111))); + this->PushBack(static_cast(0b10000000 | (Char & 0b00111111))); } else if (!(Char & ~0b0000000'00000000'11111111'11111111)) // 1110XXXX 10XXXXXX 10XXXXXX { - NativeData.PushBack(static_cast(0b11100000 | (Char >> 12 & 0b00001111))); - NativeData.PushBack(static_cast(0b10000000 | (Char >> 6 & 0b00111111))); - NativeData.PushBack(static_cast(0b10000000 | (Char & 0b00111111))); + this->PushBack(static_cast(0b11100000 | (Char >> 12 & 0b00001111))); + this->PushBack(static_cast(0b10000000 | (Char >> 6 & 0b00111111))); + this->PushBack(static_cast(0b10000000 | (Char & 0b00111111))); } else if (!(Char & ~0b0000000'11111111'11111111'11111111)) // 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX { - NativeData.PushBack(static_cast(0b11110000 | (Char >> 18 & 0b00000111))); - NativeData.PushBack(static_cast(0b10000000 | (Char >> 12 & 0b00111111))); - NativeData.PushBack(static_cast(0b10000000 | (Char >> 6 & 0b00111111))); - NativeData.PushBack(static_cast(0b10000000 | (Char & 0b00111111))); + this->PushBack(static_cast(0b11110000 | (Char >> 18 & 0b00000111))); + this->PushBack(static_cast(0b10000000 | (Char >> 12 & 0b00111111))); + this->PushBack(static_cast(0b10000000 | (Char >> 6 & 0b00111111))); + this->PushBack(static_cast(0b10000000 | (Char & 0b00111111))); } else check_no_entry(); } @@ -890,7 +898,7 @@ public: if (!Self(Self, TStringView(&WChar, 1))) return false; } - else NativeData.PushBack(static_cast(Char)); + else this->PushBack(static_cast(Char)); } else if (!(Char & ~0b0000000'00011111'11111111'11111111)) // 110110XX'XXXXXXXX 110111XX'XXXXXXXX { @@ -907,8 +915,8 @@ public: } else { - NativeData.PushBack(Buffer[0]); - NativeData.PushBack(Buffer[1]); + this->PushBack(Buffer[0]); + this->PushBack(Buffer[1]); } } else check_no_entry(); @@ -931,7 +939,7 @@ public: { return Self(Self, TStringView(reinterpret_cast(View.GetData()), View.Num())); } - else NativeData.Insert(NativeData.End(), View.Begin(), View.End()); + else Insert(this->End(), View.Begin(), View.End()); return true; } @@ -943,11 +951,8 @@ public: bool bIsValid = AppendToResult(AppendToResult, View); - if (!bIsValid) NativeData.Reset(false); - - NativeData.PushBack(LITERAL(T, '\0')); - - if (bAllowShrinking) NativeData.Shrink(); + if (!bIsValid) this->Reset(bAllowShrinking); + else if (bAllowShrinking) this->Shrink(); return bIsValid; } @@ -965,9 +970,6 @@ public: return Result; } - /** @return The non-modifiable standard C character string version of the string. */ - NODISCARD FORCEINLINE const ElementType* ToCString() const { return GetData(); } - /** @return The target-encoded string from the T-encoded string. */ NODISCARD FORCEINLINE auto ToString() const { return EncodeTo(); } NODISCARD FORCEINLINE auto ToWString() const { return EncodeTo(); } @@ -976,74 +978,46 @@ public: NODISCARD FORCEINLINE auto ToU32String() const { return EncodeTo(); } NODISCARD FORCEINLINE auto ToUnicodeString() const { return EncodeTo(); } - /** Resizes the string to contain 'Count' characters. Additional null characters are appended. */ - FORCEINLINE void SetNum(size_t Count, bool bAllowShrinking = true) { SetNum(Count, LITERAL(ElementType, '\0'), bAllowShrinking); } - - /** Resizes the string to contain 'Count' characters. Additional copies of 'InValue' are appended. */ - FORCEINLINE void SetNum(size_t Count, ElementType InValue, bool bAllowShrinking = true) { NativeData.SetNum(Count + 1, InValue, bAllowShrinking); NativeData.Back() = LITERAL(ElementType, '\0'); } - - /** Increase the max capacity of the string to a value that's greater or equal to 'Count'. */ - FORCEINLINE void Reserve(size_t Count) { NativeData.Reserve(Count + 1); } - - /** Requests the removal of unused capacity. */ - FORCEINLINE void Shrink() { NativeData.Shrink(); } - - /** @return The pointer to the underlying character storage. */ - NODISCARD FORCEINLINE ElementType* GetData() { return NativeData.GetData(); } - NODISCARD FORCEINLINE const ElementType* GetData() const { return NativeData.GetData(); } - - /** @return The iterator to the first or end character. */ - NODISCARD FORCEINLINE Iterator Begin() { return NativeData.Begin(); } - NODISCARD FORCEINLINE ConstIterator Begin() const { return NativeData.Begin(); } - NODISCARD FORCEINLINE Iterator End() { return --NativeData.End(); } - NODISCARD FORCEINLINE ConstIterator End() const { return --NativeData.End(); } - - /** @return The reverse iterator to the first or end character. */ - NODISCARD FORCEINLINE ReverseIterator RBegin() { return ++NativeData.RBegin(); } - NODISCARD FORCEINLINE ConstReverseIterator RBegin() const { return ++NativeData.RBegin(); } - NODISCARD FORCEINLINE ReverseIterator REnd() { return NativeData.REnd(); } - NODISCARD FORCEINLINE ConstReverseIterator REnd() const { return NativeData.REnd(); } - - /** @return The number of characters in the string. */ - NODISCARD FORCEINLINE size_t Num() const { return NativeData.Num() - 1; } - - /** @return The number of characters that can be held in currently allocated storage. */ - NODISCARD FORCEINLINE size_t Max() const { return NativeData.Max() - 1; } - - /** @return true if the string is empty, false otherwise. */ - NODISCARD FORCEINLINE bool IsEmpty() const { return Num() == 0; } - - /** @return true if the iterator is valid, false otherwise. */ - NODISCARD FORCEINLINE bool IsValidIterator(ConstIterator Iter) const { return Begin() <= Iter && Iter <= End(); } - - /** @return The reference to the requested character. */ - NODISCARD FORCEINLINE ElementType& operator[](size_t Index) { checkf(Index < Num(), TEXT("Read access violation. Please check IsValidIterator().")); return NativeData[Index]; } - NODISCARD FORCEINLINE const ElementType& operator[](size_t Index) const { checkf(Index < Num(), TEXT("Read access violation. Please check IsValidIterator().")); return NativeData[Index]; } - - /** @return The reference to the first or last character. */ - NODISCARD FORCEINLINE ElementType& Front() { return *Begin(); } - NODISCARD FORCEINLINE const ElementType& Front() const { return *Begin(); } - NODISCARD FORCEINLINE ElementType& Back() { return *(End() - 1); } - NODISCARD FORCEINLINE const ElementType& Back() const { return *(End() - 1); } - - /** Erases all characters from the string. After this call, Num() returns zero. */ - FORCEINLINE void Reset(bool bAllowShrinking = true) + /** @return The non-modifiable standard C character string version of the string. */ + NODISCARD FORCEINLINE auto operator*() const& { - NativeData.Reset(bAllowShrinking); - NativeData.PushBack(LITERAL(ElementType, '\0')); + if (this->Max() >= this->Num() + 1) + { + const_cast(this->GetData())[this->Num()] = LITERAL(ElementType, '\0'); + + return NAMESPACE_PRIVATE::TCStringFromTString(this->GetData(), false); + } + + if (this->Back() == LITERAL(ElementType, '\0')) + { + return NAMESPACE_PRIVATE::TCStringFromTString(this->GetData(), false); + } + + ElementType* Buffer = new ElementType[this->Num() + 1]; + + Copy(Buffer); + + Buffer[this->Num()] = LITERAL(ElementType, '\0'); + + return NAMESPACE_PRIVATE::TCStringFromTString(Buffer, true); + } + + /** @return The non-modifiable standard C character string version of the string. */ + NODISCARD FORCEINLINE auto operator*() && + { + if (this->Back() != LITERAL(T, '\0')) + { + this->PushBack(LITERAL(T, '\0')); + } + + return AsConst(*this).GetData(); } /** Overloads the GetTypeHash algorithm for TString. */ - NODISCARD friend FORCEINLINE size_t GetTypeHash(const TString& A) { return GetTypeHash(A.NativeData); } + NODISCARD friend FORCEINLINE size_t GetTypeHash(const TString& A) { return GetTypeHash(TStringView(A)); } /** Overloads the Swap algorithm for TString. */ - friend FORCEINLINE void Swap(TString& A, TString& B) { Swap(A.NativeData, B.NativeData); } - - ENABLE_RANGE_BASED_FOR_LOOP_SUPPORT - -private: - - TArray NativeData; + friend FORCEINLINE void Swap(TString& A, TString& B) { Swap(static_cast(A), static_cast(B)); } };