diff --git a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp index b8ad2ab..e18cdd6 100644 --- a/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/StringTesting.cpp @@ -2,6 +2,7 @@ #include "String/Char.h" #include "Memory/Memory.h" +#include "String/String.h" #include "String/StringView.h" #include "Miscellaneous/AssertionMacros.h" @@ -14,7 +15,8 @@ NAMESPACE_BEGIN(Testing) void TestString() { TestChar(); - TestCString(); + TestStringView(); + TestTemplateString(); } void TestChar() @@ -232,9 +234,9 @@ void TestChar() } } -void TestCString() +void TestStringView() { - auto TestTCString = [](TInPlaceType) + auto Test = [](TInPlaceType) { { TStringView Empty; @@ -314,12 +316,231 @@ void TestCString() } }; - TestTCString(InPlaceType); - TestTCString(InPlaceType); - TestTCString(InPlaceType); - TestTCString(InPlaceType); - TestTCString(InPlaceType); - TestTCString(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); +} + +void TestTemplateString() +{ + auto Test = [](TInPlaceType) + { + { + TString Empty; + + always_check(Empty.IsEmpty()); + always_check(TStringView(Empty.ToCString()) == LITERAL(T, "")); + + TString StrA(32, LITERAL(T, 'A')); + TString StrB(LITERAL(T, "ABCDEFG"), 3); + TString StrC(LITERAL(T, "ABCDEFG")); + 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")); + + TString StrI(StrC); + TString StrII(MoveTemp(StrC)); + + TString StrIII = Empty; + TString StrIV = Empty; + + 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(StrC.ToCString()) == LITERAL(T, "")); + always_check(TStringView(StrD.ToCString()) == LITERAL(T, "")); + + StrA.Reset(); + + always_check(StrA.IsEmpty()); + always_check(TStringView(StrA.ToCString()) == LITERAL(T, "")); + } + + { + TString Str = LITERAL(T, "A"); + + always_check(!Str.IsEmpty()); + always_check(Str.Num() == 1); + + always_check(Str == TString(LITERAL(T, "A"))); + always_check(Str == LITERAL(T, 'A') ); + always_check(Str == LITERAL(T, 'A') ); + always_check(TString(LITERAL(T, "A")) == Str); + always_check( LITERAL(T, 'A') == Str); + always_check( LITERAL(T, "A") == Str); + + always_check(Str != TString(LITERAL(T, "B"))); + always_check(Str != LITERAL(T, 'B') ); + always_check(Str != LITERAL(T, "B") ); + always_check(TString(LITERAL(T, "B")) != Str); + always_check( LITERAL(T, 'B') != Str); + always_check( LITERAL(T, "B") != Str); + + always_check(Str < TString(LITERAL(T, "B"))); + always_check(Str < LITERAL(T, 'B') ); + always_check(Str < LITERAL(T, "B") ); + always_check(TString(LITERAL(T, "B")) > Str); + always_check( LITERAL(T, 'B') > Str); + always_check( LITERAL(T, "B") > Str); + } + + { + TString Str = LITERAL(T, "##"); + + Str.Insert(1, LITERAL(T, 'A')); + + always_check(Str == LITERAL(T, "#A#")); + + Str.Insert(2, LITERAL(T, "BCD")); + + always_check(Str == LITERAL(T, "#ABCD#")); + + Str.Insert(3, 3, LITERAL(T, '*')); + + always_check(Str == LITERAL(T, "#AB***CD#")); + + Str.Erase(4); + + always_check(Str == LITERAL(T, "#AB**CD#")); + } + + { + TString Str = LITERAL(T, "A"); + + Str.PushBack(LITERAL(T, 'B')); + + always_check(Str == LITERAL(T, "AB")); + + Str.PopBack(); + + always_check(Str == LITERAL(T, "A")); + + Str.Append(2, LITERAL(T, 'B')); + + always_check(Str == LITERAL(T, "ABB")); + + Str.Append(LITERAL(T, "CD")); + + always_check(Str == LITERAL(T, "ABBCD")); + + Str.Append({ LITERAL(T, 'E'), LITERAL(T, 'F') }); + + always_check(Str == LITERAL(T, "ABBCDEF")); + + Str = LITERAL(T, "A"); + + Str += LITERAL(T, 'B'); + + always_check(Str == LITERAL(T, "AB")); + + Str += LITERAL(T, "CD"); + + always_check(Str == LITERAL(T, "ABCD")); + + Str += { LITERAL(T, 'E'), LITERAL(T, 'F') }; + + always_check(Str == LITERAL(T, "ABCDEF")); + } + + { + TString StrA = LITERAL(T, "A"); + TString StrB = LITERAL(T, "B"); + + always_check(StrA + StrB == LITERAL(T, "AB")); + always_check(StrA + LITERAL(T, 'B') == LITERAL(T, "AB")); + always_check(StrA + LITERAL(T, "BCD") == LITERAL(T, "ABCD")); + always_check(LITERAL(T, 'B') + StrB == LITERAL(T, "BB")); + always_check(LITERAL(T, "BCD") + StrB == LITERAL(T, "BCDB")); + + StrA = LITERAL(T, "A"); StrB = LITERAL(T, "B"); + always_check(MoveTemp(StrA) + MoveTemp(StrB) == LITERAL(T, "AB")); + StrA = LITERAL(T, "A"); StrB = LITERAL(T, "B"); + always_check(MoveTemp(StrA) + LITERAL(T, 'B') == LITERAL(T, "AB")); + StrA = LITERAL(T, "A"); StrB = LITERAL(T, "B"); + always_check(MoveTemp(StrA) + LITERAL(T, "BCD") == LITERAL(T, "ABCD")); + StrA = LITERAL(T, "A"); StrB = LITERAL(T, "B"); + always_check(LITERAL(T, 'B') + MoveTemp(StrB) == LITERAL(T, "BB")); + StrA = LITERAL(T, "A"); StrB = LITERAL(T, "B"); + always_check(LITERAL(T, "BCD") + MoveTemp(StrB) == LITERAL(T, "BCDB")); + } + + { + TString Str = LITERAL(T, "Hello, World! Goodbye, World!"); + + always_check( Str.StartsWith(LITERAL(T, "Hello, World!"))); + always_check(!Str.StartsWith(LITERAL(T, "Goodbye, World!"))); + always_check( Str.StartsWith(LITERAL(T, 'H'))); + always_check(!Str.StartsWith(LITERAL(T, 'G'))); + always_check(!Str.EndsWith(LITERAL(T, "Hello, World!"))); + always_check( Str.EndsWith(LITERAL(T, "Goodbye, World!"))); + always_check( Str.EndsWith(LITERAL(T, '!'))); + always_check(!Str.EndsWith(LITERAL(T, '?'))); + always_check( Str.Contains(LITERAL(T, "Hello, World!"))); + always_check( Str.Contains(LITERAL(T, "Goodbye, World!"))); + always_check( Str.Contains(LITERAL(T, '!'))); + always_check(!Str.Contains(LITERAL(T, '?'))); + } + + { + TString Str = LITERAL(T, "#AB**CD#"); + + always_check(Str.Replace(3, 2, 3, LITERAL(T, '^')) == LITERAL(T, "#AB^^^CD#")); + + always_check(Str.Replace(3, 3, LITERAL(T, "123")) == LITERAL(T, "#AB123CD#")); + + always_check(Str.Substr(3, 3) == LITERAL(T, "123")); + + always_check(Str.Substr(3) == LITERAL(T, "123CD#")); + } + + { + TString Str = LITERAL(T, "Hello, World! Goodbye, World!"); + + always_check(Str.Find(LITERAL(T, "")) == 0); + always_check(Str.Find(LITERAL(T, "World")) == 7); + always_check(Str.Find(LITERAL(T, 'l')) == 2); + always_check(Str.RFind(LITERAL(T, "")) == 29); + always_check(Str.RFind(LITERAL(T, "World")) == 23); + always_check(Str.RFind(LITERAL(T, 'l')) == 26); + + always_check(Str.Find(LITERAL(T, ""), 13) == 13); + always_check(Str.Find(LITERAL(T, "World"), 13) == 23); + always_check(Str.Find(LITERAL(T, 'l'), 13) == 26); + always_check(Str.RFind(LITERAL(T, ""), 13) == 13); + always_check(Str.RFind(LITERAL(T, "World"), 13) == 7); + always_check(Str.RFind(LITERAL(T, 'l'), 13) == 10); + + always_check(Str.FindFirstOf(LITERAL(T, "eor")) == 1); + always_check(Str.FindFirstOf(LITERAL(T, 'l')) == 2); + always_check(Str.FindLastOf(LITERAL(T, "eor")) == 25); + always_check(Str.FindLastOf(LITERAL(T, 'l')) == 26); + + always_check(Str.FindFirstNotOf(LITERAL(T, "Hello! Goodbye!")) == 5); + always_check(Str.FindFirstNotOf(LITERAL(T, '!')) == 0); + always_check(Str.FindLastNotOf(LITERAL(T, "Hello! Goodbye!")) == 25); + always_check(Str.FindLastNotOf(LITERAL(T, '!')) == 27); + } + }; + + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); + Test(InPlaceType); } NAMESPACE_END(Testing) diff --git a/Redcraft.Utility/Source/Public/String/String.h b/Redcraft.Utility/Source/Public/String/String.h new file mode 100644 index 0000000..86795bb --- /dev/null +++ b/Redcraft.Utility/Source/Public/String/String.h @@ -0,0 +1,722 @@ +#pragma once + +#include "CoreTypes.h" +#include "String/Char.h" +#include "Containers/Array.h" +#include "String/StringView.h" +#include "Templates/Utility.h" +#include "Templates/TypeHash.h" +#include "Templates/Container.h" +#include "Containers/Iterator.h" +#include "TypeTraits/TypeTraits.h" +#include "Miscellaneous/Compare.h" +#include "Memory/MemoryOperator.h" +#include "Memory/ObserverPointer.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)>; + +template Allocator = TDefaultStringAllocator> +class TString final +{ +public: + + using ElementType = typename TArray::ElementType; + using AllocatorType = typename TArray::AllocatorType; + + using Reference = typename TArray:: Reference; + using ConstReference = typename TArray::ConstReference; + + using Iterator = typename TArray:: Iterator; + using ConstIterator = typename TArray::ConstIterator; + + using ReverseIterator = typename TArray:: ReverseIterator; + using ConstReverseIterator = typename TArray::ConstReverseIterator; + + static_assert(CContiguousIterator< Iterator>); + static_assert(CContiguousIterator); + + /** Default constructor. Constructs an empty string. */ + FORCEINLINE TString() : NativeData({ LITERAL(ElementType, '\0') }) { } + + /** Constructs the string with 'Count' copies of characters with 'InValue'. */ + FORCEINLINE TString(size_t Count, ElementType InChar) : TString(MakeCountedConstantIterator(InChar, Count), DefaultSentinel) { } + + /** Constructs a string with the contents of the range ['InPtr', 'InPtr' + 'Count'). */ + FORCEINLINE TString(const ElementType* InPtr, size_t Count) : TString(TStringView(InPtr, Count)) + { + checkf(InPtr != nullptr, TEXT("TString cannot be initialized by nullptr. Please check the pointer.")); + } + + FORCEINLINE TString(nullptr_t, size_t) = delete; + + /** Constructs a string with the contents of the range ['InPtr', '\0'). */ + FORCEINLINE TString(const ElementType* InPtr) : TString(TStringView(InPtr)) + { + checkf(InPtr != nullptr, TEXT("TString cannot be initialized by nullptr. Please check the pointer.")); + } + + FORCEINLINE TString(nullptr_t) = delete; + + /** Constructs the string with the contents of the 'View'. */ + FORCEINLINE TString(TStringView View) : TString(View.Begin(), View.End()) { } + + /** 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')); + } + } + + /** 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')); } + + /** 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; } + + /** Compares the contents of two strings. */ + FORCEINLINE NODISCARD friend bool operator==(const TString& LHS, const TString& RHS) { return TStringView(LHS) == TStringView(RHS); } + + /** Compares the contents of a string and a character. */ + FORCEINLINE NODISCARD friend bool operator==(const TString& LHS, ElementType RHS) { return TStringView(LHS) == RHS; } + FORCEINLINE NODISCARD friend bool operator==(const TString& LHS, const ElementType* RHS) { return TStringView(LHS) == RHS; } + FORCEINLINE NODISCARD friend bool operator==( ElementType LHS, const TString& RHS) { return LHS == TStringView(RHS); } + FORCEINLINE NODISCARD friend bool operator==(const ElementType* LHS, const TString& RHS) { return LHS == TStringView(RHS); } + + /** Compares the contents of 'LHS' and 'RHS' lexicographically. */ + FORCEINLINE NODISCARD friend auto operator<=>(const TString& LHS, const TString& RHS) { return TStringView(LHS) <=> TStringView(RHS); } + + /** Compares the contents of 'LHS' and 'RHS' lexicographically. */ + FORCEINLINE NODISCARD friend auto operator<=>(const TString& LHS, ElementType RHS) { return TStringView(LHS) <=> RHS; } + FORCEINLINE NODISCARD friend auto operator<=>(const TString& LHS, const ElementType* RHS) { return TStringView(LHS) <=> RHS; } + FORCEINLINE NODISCARD friend auto operator<=>( ElementType LHS, const TString& RHS) { return LHS <=> TStringView(RHS); } + FORCEINLINE NODISCARD friend auto operator<=>(const ElementType* LHS, const TString& RHS) { return LHS <=> TStringView(RHS); } + + /** 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().")); + + return Insert(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().")); + + return NativeData.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().")); + + return Insert(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().")); + + return NativeData.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().")); + + return Insert(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().")); + + return Insert(Iter, View.Begin(), View.End()); + } + + /** Inserts characters from the range ['First', 'Last') before 'Index' in the string. */ + template S> requires (CConstructibleFrom>) + FORCEINLINE Iterator Insert(size_t Index, I First, S Last) + { + checkf(Index <= Num(), TEXT("Illegal index. Please check Index <= Num().")); + + return Insert(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().")); + + return NativeData.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().")); + + return Insert(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().")); + + return NativeData.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().")); + + return Erase(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().")); + + return NativeData.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.")); + + return Erase(Begin() + Index, 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().")); + + return NativeData.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'); } + + /** 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); } + + /** Appends the contents of the 'View' to the end of the string. */ + FORCEINLINE TString& Append(TStringView View) { return Append(View.Begin(), View.End()); } + + /** Appends the contents of the range ['First', 'Last') to the end of the string. */ + template S> requires (CConstructibleFrom>) + TString& Append(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); + + const size_t CurrentNum = Num(); + + NativeData.SetNum(CurrentNum + Count + 1); + + for (size_t Index = CurrentNum; Index != CurrentNum + Count; ++Index) + { + NativeData[Index] = ElementType(*First++); + } + + NativeData.Back() = LITERAL(ElementType, '\0'); + } + else + { + NativeData.Insert(NativeData.End() - 1, MoveTemp(First), MoveTemp(Last)); + + NativeData.PushBack(LITERAL(ElementType, '\0')); + } + + return *this; + } + + /** Appends the contents of the initializer list to the end of the string. */ + FORCEINLINE TString& Append(initializer_list IL) { return Append(Iteration::Begin(IL), Iteration::End(IL)); } + + /** Appends the given character value to the end of the string. */ + FORCEINLINE TString& operator+=(ElementType InChar) { return Append(1, InChar); } + + /** Appends the contents of the 'View' to the end of the string. */ + FORCEINLINE TString& operator+=(TStringView View) { return Append(View); } + + /** Appends the contents of the range ['First', 'Last') to the end of the string. */ + FORCEINLINE TString& operator+=(initializer_list IL) { return Append(IL); } + + /** Concatenates two strings. */ + NODISCARD friend FORCEINLINE TString operator+(const TString& LHS, const TString& RHS) { return TString(LHS).Append(RHS); } + + /** Concatenates the string with the given character value. */ + NODISCARD friend FORCEINLINE TString operator+(const TString& LHS, ElementType RHS) { return TString(LHS).Append(1, RHS); } + NODISCARD friend FORCEINLINE TString operator+(const TString& LHS, const ElementType* RHS) { return TString(LHS).Append( RHS); } + NODISCARD friend FORCEINLINE TString operator+(const TString& LHS, TStringView RHS) { return TString(LHS).Append( RHS); } + NODISCARD friend FORCEINLINE TString operator+( ElementType LHS, const TString& RHS) { return TString(1, LHS).Append(RHS); } + NODISCARD friend FORCEINLINE TString operator+( const ElementType* LHS, const TString& RHS) { return TString( LHS).Append(RHS); } + NODISCARD friend FORCEINLINE TString operator+(TStringView LHS, const TString& RHS) { return TString( LHS).Append(RHS); } + + /** Concatenates two strings. The rvalue maybe modified. */ + NODISCARD friend FORCEINLINE TString operator+(TString&& LHS, TString&& RHS) { LHS.Append(MoveTemp(RHS)); return LHS; } + + /** Concatenates two strings. The rvalue maybe modified. */ + NODISCARD friend FORCEINLINE TString operator+(TString&& LHS, ElementType RHS) { LHS.Append(1, RHS); return LHS; } + NODISCARD friend FORCEINLINE TString operator+(TString&& LHS, const ElementType* RHS) { LHS.Append( RHS); return LHS; } + NODISCARD friend FORCEINLINE TString operator+(TString&& LHS, TStringView RHS) { LHS.Append( RHS); return LHS; } + NODISCARD friend FORCEINLINE TString operator+(TString&& LHS, const TString& RHS) { LHS.Append( RHS); return LHS; } + NODISCARD friend FORCEINLINE TString operator+( ElementType LHS, TString&& RHS) { RHS.Insert(0, LHS); return RHS; } + NODISCARD friend FORCEINLINE TString operator+( const ElementType* LHS, TString&& RHS) { RHS.Insert(0, LHS); return RHS; } + NODISCARD friend FORCEINLINE TString operator+( TStringView LHS, TString&& RHS) { RHS.Insert(0, LHS); return RHS; } + NODISCARD friend FORCEINLINE TString operator+(const TString& LHS, TString&& RHS) { RHS.Insert(0, LHS); return RHS; } + + /** @return true if the string view starts with the given prefix, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool StartsWith(TStringView Prefix) const + { + return TStringView(*this).StartsWith(Prefix); + } + + /** @return true if the string view starts with the given prefix, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool StartsWith(ElementType Prefix) const + { + return TStringView(*this).StartsWith(Prefix); + } + + /** @return true if the string view ends with the given suffix, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool EndsWith(TStringView Suffix) const + { + return TStringView(*this).EndsWith(Suffix); + } + + /** @return true if the string view ends with the given suffix, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool EndsWith(ElementType Suffix) const + { + return TStringView(*this).EndsWith(Suffix); + } + + /** @return true if the string view contains the given substring, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool Contains(TStringView View) const + { + return TStringView(*this).Contains(View); + } + + /** @return true if the string view contains the given character, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool Contains(ElementType Char) const + { + return TStringView(*this).Contains(Char); + } + + /** @return true if the string view contains character that satisfy the given predicate, false otherwise. */ + template F> + NODISCARD FORCEINLINE constexpr bool Contains(F&& InPredicate) const + { + return TStringView(*this).Contains(Forward(InPredicate)); + } + + /** 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.")); + + return Replace(Begin() + Index, 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().")); + + return Replace(First, Last, MakeCountedConstantIterator(InChar, Count), DefaultSentinel); + } + + /** 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.")); + + return Replace(Begin() + Index, 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().")); + + return Replace(First, Last, View.Begin(), View.End()); + } + + /** Replace the substring [Index, Index + CountToReplace) with the contents of the range ['First', 'Last'). */ + 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.")); + + return Replace(Begin() + Index, 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().")); + + 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 RemoveCount = Iteration::Distance(First, Last); + const size_t InsertCount = Iteration::Distance(InString, Sentinel); + + const size_t NumToReset = Num() - RemoveCount + InsertCount; + + if (InsertCount < RemoveCount) + { + for (size_t Index = InsertIndex; Index != InsertIndex + InsertCount; ++Index) + { + NativeData[Index] = ElementType(*InString++); + } + + for (size_t Index = InsertIndex + InsertCount; Index != NumToReset; ++Index) + { + NativeData[Index] = NativeData[Index + (RemoveCount - InsertCount)]; + } + + NativeData.SetNum(NumToReset + 1); + } + else + { + NativeData.SetNum(NumToReset + 1); + + for (size_t Index = Num(); Index != InsertIndex + InsertCount - 1; --Index) + { + NativeData[Index - 1] = NativeData[Index + (RemoveCount - InsertCount) - 1]; + } + + for (size_t Index = InsertIndex; Index != InsertIndex + InsertCount; ++Index) + { + NativeData[Index] = ElementType(*InString++); + } + } + + NativeData.Back() = LITERAL(ElementType, '\0'); + } + else + { + TString Temp(MoveTemp(First), MoveTemp(Last)); + return Replace(First, Last, Temp.Begin(), Temp.End()); + } + + return *this; + } + + /** 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.")); + + return Replace(Begin() + Index, 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().")); + + return Replace(First, Last, Iteration::Begin(IL), Iteration::End(IL)); + } + + /** 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.")); + + return TStringView(*this).Substr(Offset, Count); + } + + /** Copies the characters of this string to the destination buffer without null-termination. */ + FORCEINLINE constexpr size_t Copy(ElementType* Dest, size_t Count = DynamicExtent, size_t Offset = 0) const + { + 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.")); + + return TStringView(*this).Copy(Dest, Count, Offset); + } + + FORCEINLINE constexpr size_t Copy(nullptr_t, size_t Count = DynamicExtent, size_t Offset = 0) const = delete; + + /** @return The index of the first occurrence of the given substring, or INDEX_NONE if not found. */ + NODISCARD constexpr size_t Find(TStringView View, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).Find(View, Index); + } + + /** @return The index of the first occurrence of the given character, or INDEX_NONE if not found. */ + NODISCARD constexpr size_t Find(ElementType Char, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).Find(Char, Index); + } + + /** @return The index of the first occurrence of the character that satisfy the given predicate, or INDEX_NONE if not found. */ + template F> + NODISCARD constexpr size_t Find(F&& InPredicate, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).Find(Forward(InPredicate), Index); + } + + /** @return The index of the last occurrence of the given substring, or INDEX_NONE if not found. */ + NODISCARD constexpr size_t RFind(TStringView View, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).RFind(View, Index); + } + + /** @return The index of the last occurrence of the given character, or INDEX_NONE if not found. */ + NODISCARD constexpr size_t RFind(ElementType Char, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).RFind(Char, Index); + } + + /** @return The index of the last occurrence of the character that satisfy the given predicate, or INDEX_NONE if not found. */ + template F> + NODISCARD constexpr size_t RFind(F&& InPredicate, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).RFind(Forward(InPredicate), Index); + } + + /** @return The index of the first occurrence of the character contained in the given view, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindFirstOf(TStringView View, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).FindFirstOf(View, Index); + } + + /** @return The index of the first occurrence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindFirstOf(ElementType Char, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).FindFirstOf(Char, Index); + } + + /** @return The index of the last occurrence of the character contained in the given view, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindLastOf(TStringView View, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).FindLastOf(View, Index); + } + + /** @return The index of the last occurrence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindLastOf(ElementType Char, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).FindLastOf(Char, Index); + } + + /** @return The index of the first absence of the character contained in the given view, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindFirstNotOf(TStringView View, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).FindFirstNotOf(View, Index); + } + + /** @return The index of the first absence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindFirstNotOf(ElementType Char, size_t Index = 0) const + { + checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).FindFirstNotOf(Char, Index); + } + + /** @return The index of the last absence of the character contained in the given view, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindLastNotOf(TStringView View, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).FindLastNotOf(View, Index); + } + + /** @return The index of the last absence of the given character, or INDEX_NONE if not found. */ + NODISCARD FORCEINLINE constexpr size_t FindLastNotOf(ElementType Char, size_t Index = INDEX_NONE) const + { + checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); + + return TStringView(*this).FindLastNotOf(Char, Index); + } + + /** 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 TObserverPtr< ElementType[]> GetData() { return NativeData.GetData(); } + NODISCARD FORCEINLINE TObserverPtr GetData() const { return NativeData.GetData(); } + + /** @return The non-modifiable standard C character string version of the string. */ + NODISCARD FORCEINLINE const ElementType* ToCString() const { return NativeData.GetData().Get(); } + + /** @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) + { + NativeData.Reset(bAllowShrinking); + NativeData.PushBack(LITERAL(ElementType, '\0')); + } + + /** Overloads the GetTypeHash algorithm for TString. */ + NODISCARD friend FORCEINLINE size_t GetTypeHash(const TString& A) { return GetTypeHash(A.NativeData); } + + /** 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; + +}; + +template +TString(size_t, T) -> TString; + +template +TString(const T*) -> TString; + +template +TString(TStringView) -> TString; + +template +TString(I, S) -> TString>; + +template +TString(initializer_list) -> TString; + +using FString = TString; +using FWString = TString; +using FU8String = TString; +using FU16String = TString; +using FU32String = TString; +using FUnicodeString = TString; + +template template constexpr TStringView::TStringView(const TString& InString) + : TStringView(InString.GetData().Get(), InString.Num()) { } + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/String/StringView.h b/Redcraft.Utility/Source/Public/String/StringView.h index d1eb671..6d33abe 100644 --- a/Redcraft.Utility/Source/Public/String/StringView.h +++ b/Redcraft.Utility/Source/Public/String/StringView.h @@ -10,8 +10,10 @@ #include "Containers/ArrayView.h" #include "TypeTraits/TypeTraits.h" #include "Miscellaneous/Compare.h" +#include "Memory/MemoryOperator.h" #include "Memory/ObserverPointer.h" #include "Miscellaneous/AssertionMacros.h" +#include "Miscellaneous/ConstantIterator.h" #include #include @@ -20,6 +22,9 @@ NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) +template Allocator> +class TString; + /** * The class template TStringView describes an object that can refer to a constant contiguous sequence of char-like objects * with the first element of the sequence at position zero. Provides a set of convenient string processing functions. @@ -49,6 +54,10 @@ public: template S> requires (CConvertibleTo(*)[], const ElementType(*)[]>) FORCEINLINE constexpr TStringView(I InFirst, S InLast) : NativeData(InFirst, InLast) { } + /** Constructs a string view that is a view over the string 'InString'. */ + template + FORCEINLINE constexpr TStringView(const TString& InString); + /** Constructs a string view that is a view over the range ['InPtr', 'InPtr' + 'Count'). */ FORCEINLINE constexpr TStringView(const ElementType* InPtr, size_t Count) : NativeData(InPtr, Count) { @@ -91,9 +100,17 @@ public: /** Compares the contents of two string views. */ NODISCARD friend constexpr bool operator==(TStringView LHS, TStringView RHS) { return LHS.NativeData == RHS.NativeData; } + /** Compares the contents of a string view and a character. */ + NODISCARD friend constexpr bool operator==(TStringView LHS, ElementType RHS) { return LHS == TStringView(&RHS, 1); } + NODISCARD friend constexpr bool operator==(ElementType LHS, TStringView RHS) { return TStringView(&LHS, 1) == RHS; } + /** Compares the contents of two string views. */ NODISCARD friend constexpr auto operator<=>(TStringView LHS, TStringView RHS) { return LHS.NativeData <=> RHS.NativeData; } + /** Compares the contents of a string view and a character. */ + NODISCARD friend constexpr auto operator<=>(TStringView LHS, ElementType RHS) { return LHS <=> TStringView(&RHS, 1); } + NODISCARD friend constexpr auto operator<=>(ElementType LHS, TStringView RHS) { return TStringView(&LHS, 1) <=> RHS; } + /** Shrinks the view by moving its start forward. */ FORCEINLINE constexpr void RemovePrefix(size_t Count) { @@ -129,6 +146,8 @@ public: /** Copies the elements of this string view to the destination buffer without null-termination. */ FORCEINLINE constexpr size_t Copy(ElementType* Dest, size_t Count = DynamicExtent, size_t Offset = 0) const { + 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.")); if (Count == DynamicExtent) @@ -136,11 +155,13 @@ public: Count = Num() - Offset; } - Memory::Memcpy(Dest, GetData().Get() + Offset, Count * sizeof(ElementType)); + Memory::CopyAssign(Dest, GetData().Get() + Offset, Count); return Count; } + FORCEINLINE constexpr size_t Copy(nullptr_t, size_t Count = DynamicExtent, size_t Offset = 0) const = delete; + /** Obtains a string view that is a view over the 'Count' elements of this string view starting at 'Offset'. */ NODISCARD FORCEINLINE constexpr TStringView Substr(size_t Offset, size_t Count = DynamicExtent) const { @@ -208,7 +229,7 @@ public: } /** @return The index of the first occurrence of the given substring, or INDEX_NONE if not found. */ - NODISCARD FORCEINLINE constexpr size_t Find(TStringView View, size_t Index = 0) const + NODISCARD constexpr size_t Find(TStringView View, size_t Index = 0) const { checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); @@ -228,7 +249,7 @@ public: } /** @return The index of the first occurrence of the given character, or INDEX_NONE if not found. */ - NODISCARD FORCEINLINE constexpr size_t Find(ElementType Char, size_t Index = 0) const + NODISCARD constexpr size_t Find(ElementType Char, size_t Index = 0) const { checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); @@ -245,7 +266,7 @@ public: /** @return The index of the first occurrence of the character that satisfy the given predicate, or INDEX_NONE if not found. */ template F> - NODISCARD FORCEINLINE constexpr size_t Find(F&& InPredicate, size_t Index = 0) const + NODISCARD constexpr size_t Find(F&& InPredicate, size_t Index = 0) const { checkf(Index < Num(), TEXT("Illegal index. Please check Index.")); @@ -261,7 +282,7 @@ public: } /** @return The index of the last occurrence of the given substring, or INDEX_NONE if not found. */ - NODISCARD FORCEINLINE constexpr size_t RFind(TStringView View, size_t Index = INDEX_NONE) const + NODISCARD constexpr size_t RFind(TStringView View, size_t Index = INDEX_NONE) const { checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); @@ -283,7 +304,7 @@ public: } /** @return The index of the last occurrence of the given character, or INDEX_NONE if not found. */ - NODISCARD FORCEINLINE constexpr size_t RFind(ElementType Char, size_t Index = INDEX_NONE) const + NODISCARD constexpr size_t RFind(ElementType Char, size_t Index = INDEX_NONE) const { checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); @@ -302,7 +323,7 @@ public: /** @return The index of the last occurrence of the character that satisfy the given predicate, or INDEX_NONE if not found. */ template F> - NODISCARD FORCEINLINE constexpr size_t RFind(F&& InPredicate, size_t Index = INDEX_NONE) const + NODISCARD constexpr size_t RFind(F&& InPredicate, size_t Index = INDEX_NONE) const { checkf(Index == INDEX_NONE || Index < Num(), TEXT("Illegal index. Please check Index.")); @@ -424,6 +445,9 @@ private: template TStringView(I, S) -> TStringView>; +template +TStringView(TString) -> TStringView; + using FStringView = TStringView; using FWStringView = TStringView; using FU8StringView = TStringView; diff --git a/Redcraft.Utility/Source/Public/Testing/StringTesting.h b/Redcraft.Utility/Source/Public/Testing/StringTesting.h index db7205f..87d3b82 100644 --- a/Redcraft.Utility/Source/Public/Testing/StringTesting.h +++ b/Redcraft.Utility/Source/Public/Testing/StringTesting.h @@ -10,7 +10,8 @@ NAMESPACE_BEGIN(Testing) REDCRAFTUTILITY_API void TestString(); REDCRAFTUTILITY_API void TestChar(); -REDCRAFTUTILITY_API void TestCString(); +REDCRAFTUTILITY_API void TestStringView(); +REDCRAFTUTILITY_API void TestTemplateString(); NAMESPACE_END(Testing)