refactor(string): use end sentinel instead of buffer size to simplify implementation and usage

This commit is contained in:
Redstone1024 2024-09-28 21:07:06 +08:00
parent d52f0c4df8
commit 6c9beec4be
2 changed files with 197 additions and 205 deletions

View File

@ -242,149 +242,161 @@ void TestCString()
T StrC[BUFFER_SIZE]; T StrC[BUFFER_SIZE];
T StrD[BUFFER_SIZE]; T StrD[BUFFER_SIZE];
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr); const T* EndA = &StrA[BUFFER_SIZE];
always_check(TCString<T>::Copy(StrB, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr); const T* EndB = &StrB[BUFFER_SIZE];
always_check(TCString<T>::Copy(StrC, IGNORE_SIZE, LITERAL(T, "World"), IGNORE_SIZE) != nullptr); const T* EndC = &StrC[BUFFER_SIZE];
always_check(TCString<T>::Copy(StrD, IGNORE_SIZE, LITERAL(T, " "), IGNORE_SIZE) != nullptr); const T* EndD = &StrD[BUFFER_SIZE];
always_check(TCString<T>::Length(StrA, 4) == 4); always_check(TCString<T>::Copy(StrA, nullptr, LITERAL(T, "Hello"), nullptr) != nullptr);
always_check(TCString<T>::Length(StrA, BUFFER_SIZE) == 5); always_check(TCString<T>::Copy(StrB, nullptr, LITERAL(T, "Hello"), nullptr) != nullptr);
always_check(TCString<T>::Length(StrA, IGNORE_SIZE) == 5); always_check(TCString<T>::Copy(StrC, nullptr, LITERAL(T, "World"), nullptr) != nullptr);
always_check(TCString<T>::Copy(StrD, nullptr, LITERAL(T, " "), nullptr) != nullptr);
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrB, IGNORE_SIZE) == TCString<T>::Compare(StrA, BUFFER_SIZE, StrB, BUFFER_SIZE)); always_check(TCString<T>::Length(StrA, &StrA[4]) == 4);
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrC, IGNORE_SIZE) == TCString<T>::Compare(StrA, BUFFER_SIZE, StrC, BUFFER_SIZE)); always_check(TCString<T>::Length(StrA, EndA ) == 5);
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrC, IGNORE_SIZE) < 0); always_check(TCString<T>::Length(StrA, nullptr ) == 5);
const T* PtrA = LITERAL(T, "Hel");
const T* PtrB = LITERAL(T, "Hello");
always_check(TCString<T>::Compare(PtrA, nullptr, PtrB, &PtrB[3]) == 0);
always_check(TCString<T>::Compare(StrA, nullptr, StrB, nullptr) == TCString<T>::Compare(StrA, EndA, StrB, EndB));
always_check(TCString<T>::Compare(StrA, nullptr, StrC, nullptr) == TCString<T>::Compare(StrA, EndA, StrC, EndC));
always_check(TCString<T>::Compare(StrA, nullptr, StrC, nullptr) < 0);
Memory::Memzero(StrD); Memory::Memzero(StrD);
always_check(TCString<T>::Compare(StrA, BUFFER_SIZE, StrD, BUFFER_SIZE) > 0); always_check(TCString<T>::Compare(StrA, EndA , StrD, EndD ) > 0);
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) > 0); always_check(TCString<T>::Compare(StrA, nullptr, StrD, nullptr) > 0);
always_check(TCString<T>::Copy(StrD, IGNORE_SIZE, StrA, IGNORE_SIZE) != nullptr); always_check(TCString<T>::Copy(StrD, nullptr, StrA, nullptr) != nullptr);
always_check(TCString<T>::Compare(StrA, BUFFER_SIZE, StrD, BUFFER_SIZE) == 0); always_check(TCString<T>::Compare(StrA, EndA , StrD, EndD ) == 0);
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) == 0); always_check(TCString<T>::Compare(StrA, nullptr, StrD, nullptr) == 0);
Memory::Memzero(StrC); Memory::Memzero(StrC);
Memory::Memzero(StrD); Memory::Memzero(StrD);
always_check(TCString<T>::Copy(StrD, 4, StrA, IGNORE_SIZE) == nullptr); always_check(TCString<T>::Copy(StrD, &StrD[4], StrA, nullptr) == nullptr);
always_check(TCString<T>::Compare(StrC, BUFFER_SIZE, StrD, BUFFER_SIZE) == 0); always_check(TCString<T>::Compare(StrC, EndC , StrD, EndD ) == 0);
always_check(TCString<T>::Compare(StrC, IGNORE_SIZE, StrD, IGNORE_SIZE) == 0); always_check(TCString<T>::Compare(StrC, nullptr, StrD, nullptr) == 0);
always_check(TCString<T>::Copy(StrD, IGNORE_SIZE, StrA, 4) != nullptr); always_check(TCString<T>::Copy(StrD, nullptr, StrA, &StrA[4]) != nullptr);
always_check(TCString<T>::Length(StrD, IGNORE_SIZE) == 4); always_check(TCString<T>::Length(StrD, nullptr) == 4);
always_check(TCString<T>::Compare(StrA, 4, StrD, 4) == 0); always_check(TCString<T>::Compare(StrA, &StrA[4], StrD, &StrD[4]) == 0);
always_check(TCString<T>::Compare(StrA, IGNORE_SIZE, StrD, IGNORE_SIZE) > 0); always_check(TCString<T>::Compare(StrA, nullptr , StrD, nullptr ) > 0);
always_check(TCString<T>::Copy( StrB, IGNORE_SIZE, LITERAL(T, "World!"), 5) != nullptr); const T* PtrC = LITERAL(T, "World!");
always_check(TCString<T>::Compare(StrB, IGNORE_SIZE, LITERAL(T, "World" ), IGNORE_SIZE) == 0);
always_check(TCString<T>::Copy( StrB, nullptr, PtrC, &PtrC[5]) != nullptr);
always_check(TCString<T>::Compare(StrB, nullptr, LITERAL(T, "World"), nullptr ) == 0);
Memory::Memzero(StrD); Memory::Memzero(StrD);
always_check(TCString<T>::Cat(StrD, 8, StrA, IGNORE_SIZE) != nullptr); always_check(TCString<T>::Cat(StrD, &StrD[8], StrA, nullptr) != nullptr);
always_check(TCString<T>::Cat(StrD, 8, LITERAL(T, " "), IGNORE_SIZE) != nullptr); always_check(TCString<T>::Cat(StrD, &StrD[8], LITERAL(T, " "), nullptr) != nullptr);
always_check(TCString<T>::Cat(StrD, 8, StrB, IGNORE_SIZE) == nullptr); always_check(TCString<T>::Cat(StrD, &StrD[8], StrB, nullptr) == nullptr);
always_check(TCString<T>::Compare(StrD, IGNORE_SIZE, LITERAL(T, "Hello "), IGNORE_SIZE) == 0); always_check(TCString<T>::Compare(StrD, nullptr, LITERAL(T, "Hello "), nullptr) == 0);
Memory::Memzero(StrD); Memory::Memzero(StrD);
always_check(TCString<T>::Cat(StrD, IGNORE_SIZE, StrA, IGNORE_SIZE) != nullptr); always_check(TCString<T>::Cat(StrD, nullptr, StrA, nullptr) != nullptr);
always_check(TCString<T>::Cat(StrD, IGNORE_SIZE, LITERAL(T, " "), IGNORE_SIZE) != nullptr); always_check(TCString<T>::Cat(StrD, nullptr, LITERAL(T, " "), nullptr) != nullptr);
always_check(TCString<T>::Cat(StrD, IGNORE_SIZE, StrB, IGNORE_SIZE) != nullptr); always_check(TCString<T>::Cat(StrD, nullptr, StrB, nullptr) != nullptr);
always_check(TCString<T>::Compare(StrD, IGNORE_SIZE, LITERAL(T, "Hello World"), IGNORE_SIZE) == 0); always_check(TCString<T>::Compare(StrD, nullptr, LITERAL(T, "Hello World"), nullptr) == 0);
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "Hello"), IGNORE_SIZE) != nullptr); always_check(TCString<T>::Copy(StrA, nullptr, LITERAL(T, "Hello"), nullptr) != nullptr);
always_check(TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5); always_check(TCString<T>::Find(StrA, nullptr , [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5);
always_check(TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5); always_check(TCString<T>::Find(StrA, EndA , [](T A) { return A == LITERAL(T, '\0'); }) == StrA + 5);
always_check(TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); }) == StrA + 4); always_check(TCString<T>::Find(StrA, nullptr , [](T A) { return A == LITERAL(T, 'o'); }) == StrA + 4);
always_check(TCString<T>::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); }) == nullptr); always_check(TCString<T>::Find(StrA, &StrA[4], [](T A) { return A == LITERAL(T, 'o'); }) == nullptr);
always_check(TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); }) always_check(TCString<T>::Find(StrA, nullptr, [](T A) { return A == LITERAL(T, 'o'); })
== TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); == TCString<T>::Find(StrA, nullptr, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd));
always_check(TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'l'); }) always_check(TCString<T>::Find(StrA, nullptr, [](T A) { return A == LITERAL(T, 'l'); })
!= TCString<T>::Find(StrA, IGNORE_SIZE, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); != TCString<T>::Find(StrA, nullptr, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd));
always_check(TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'o'); }) always_check(TCString<T>::Find(StrA, EndA, [](T A) { return A == LITERAL(T, 'o'); })
== TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); == TCString<T>::Find(StrA, EndA, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd));
always_check(TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'l'); }) always_check(TCString<T>::Find(StrA, EndA, [](T A) { return A == LITERAL(T, 'l'); })
!= TCString<T>::Find(StrA, BUFFER_SIZE, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); != TCString<T>::Find(StrA, EndA, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd));
always_check(TCString<T>::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); }) always_check(TCString<T>::Find(StrA, &StrA[4], [](T A) { return A == LITERAL(T, 'o'); })
== TCString<T>::Find(StrA, 4, [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd)); == TCString<T>::Find(StrA, &StrA[4], [](T A) { return A == LITERAL(T, 'o'); }, ESearchDirection::FromEnd));
always_check(TCString<T>::Find(StrA, 3, [](T A) { return A == LITERAL(T, 'l'); }) always_check(TCString<T>::Find(StrA, &StrA[3], [](T A) { return A == LITERAL(T, 'l'); })
== TCString<T>::Find(StrA, 3, [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd)); == TCString<T>::Find(StrA, &StrA[3], [](T A) { return A == LITERAL(T, 'l'); }, ESearchDirection::FromEnd));
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, '\0')) == StrA + 5); always_check(TCString<T>::FindChar(StrA, nullptr , LITERAL(T, '\0')) == StrA + 5);
always_check(TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, '\0')) == StrA + 5); always_check(TCString<T>::FindChar(StrA, EndA , LITERAL(T, '\0')) == StrA + 5);
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o')) == StrA + 4); always_check(TCString<T>::FindChar(StrA, nullptr , LITERAL(T, 'o')) == StrA + 4);
always_check(TCString<T>::FindChar(StrA, 4, LITERAL(T, 'o')) == nullptr); always_check(TCString<T>::FindChar(StrA, &StrA[4], LITERAL(T, 'o')) == nullptr);
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o')) always_check(TCString<T>::FindChar(StrA, nullptr, LITERAL(T, 'o'))
== TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'o'), ESearchDirection::FromEnd)); == TCString<T>::FindChar(StrA, nullptr, LITERAL(T, 'o'), ESearchDirection::FromEnd));
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'l')) always_check(TCString<T>::FindChar(StrA, nullptr, LITERAL(T, 'l'))
!= TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, 'l'), ESearchDirection::FromEnd)); != TCString<T>::FindChar(StrA, nullptr, LITERAL(T, 'l'), ESearchDirection::FromEnd));
always_check(TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'o')) always_check(TCString<T>::FindChar(StrA, EndA, LITERAL(T, 'o'))
== TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'o'), ESearchDirection::FromEnd)); == TCString<T>::FindChar(StrA, EndA, LITERAL(T, 'o'), ESearchDirection::FromEnd));
always_check(TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'l')) always_check(TCString<T>::FindChar(StrA, EndA, LITERAL(T, 'l'))
!= TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, 'l'), ESearchDirection::FromEnd)); != TCString<T>::FindChar(StrA, EndA, LITERAL(T, 'l'), ESearchDirection::FromEnd));
always_check(TCString<T>::FindChar(StrA, 4, LITERAL(T, 'o')) always_check(TCString<T>::FindChar(StrA, &StrA[4], LITERAL(T, 'o'))
== TCString<T>::FindChar(StrA, 4, LITERAL(T, 'o'), ESearchDirection::FromEnd)); == TCString<T>::FindChar(StrA, &StrA[4], LITERAL(T, 'o'), ESearchDirection::FromEnd));
always_check(TCString<T>::FindChar(StrA, 3, LITERAL(T, 'l')) always_check(TCString<T>::FindChar(StrA, &StrA[3], LITERAL(T, 'l'))
== TCString<T>::FindChar(StrA, 3, LITERAL(T, 'l'), ESearchDirection::FromEnd)); == TCString<T>::FindChar(StrA, &StrA[3], LITERAL(T, 'l'), ESearchDirection::FromEnd));
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE) == nullptr); always_check(TCString<T>::FindChar(StrA, nullptr , LITERAL(T, ""), nullptr) == nullptr);
always_check(TCString<T>::FindChar(StrA, BUFFER_SIZE, LITERAL(T, ""), IGNORE_SIZE) == nullptr); always_check(TCString<T>::FindChar(StrA, EndA , LITERAL(T, ""), nullptr) == nullptr);
always_check(TCString<T>::FindChar(StrA, IGNORE_SIZE, LITERAL(T, "o"), IGNORE_SIZE) == StrA + 4); always_check(TCString<T>::FindChar(StrA, nullptr , LITERAL(T, "o"), nullptr) == StrA + 4);
always_check(TCString<T>::FindChar(StrA, 4, LITERAL(T, "o"), IGNORE_SIZE) == nullptr); always_check(TCString<T>::FindChar(StrA, &StrA[4], LITERAL(T, "o"), nullptr) == nullptr);
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "HIH"), IGNORE_SIZE) != nullptr); always_check(TCString<T>::Copy(StrA, nullptr, LITERAL(T, "HIH"), nullptr) != nullptr);
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, '\0')) == StrA); always_check(TCString<T>::FindNotChar(StrA, nullptr , LITERAL(T, '\0')) == StrA);
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, '\0')) == StrA); always_check(TCString<T>::FindNotChar(StrA, EndA , LITERAL(T, '\0')) == StrA);
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, 'I')) == StrA); always_check(TCString<T>::FindNotChar(StrA, nullptr , LITERAL(T, 'I')) == StrA);
always_check(TCString<T>::FindNotChar(StrA, 2, LITERAL(T, 'I')) == StrA); always_check(TCString<T>::FindNotChar(StrA, &StrA[2], LITERAL(T, 'I')) == StrA);
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2); always_check(TCString<T>::FindNotChar(StrA, nullptr , LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2);
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2); always_check(TCString<T>::FindNotChar(StrA, EndA , LITERAL(T, '\0'), ESearchDirection::FromEnd) == StrA + 2);
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 3); always_check(TCString<T>::FindNotChar(StrA, nullptr , LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 3);
always_check(TCString<T>::FindNotChar(StrA, 2, LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 0); always_check(TCString<T>::FindNotChar(StrA, &StrA[2], LITERAL(T, 'I'), ESearchDirection::FromEnd) == StrA + 0);
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "HIJIH"), IGNORE_SIZE) != nullptr); always_check(TCString<T>::Copy(StrA, nullptr, LITERAL(T, "HIJIH"), nullptr) != nullptr);
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "HIJ"), IGNORE_SIZE) == nullptr); always_check(TCString<T>::FindNotChar(StrA, nullptr, LITERAL(T, "HIJ"), nullptr) == nullptr);
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "HIJ"), IGNORE_SIZE) == nullptr); always_check(TCString<T>::FindNotChar(StrA, EndA , LITERAL(T, "HIJ"), nullptr) == nullptr);
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "H J"), IGNORE_SIZE) == StrA + 1); always_check(TCString<T>::FindNotChar(StrA, nullptr, LITERAL(T, "H J"), nullptr) == StrA + 1);
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "H J"), IGNORE_SIZE) == StrA + 1); always_check(TCString<T>::FindNotChar(StrA, EndA , LITERAL(T, "H J"), nullptr) == StrA + 1);
always_check(TCString<T>::FindNotChar(StrA, IGNORE_SIZE, LITERAL(T, "H J"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 3); always_check(TCString<T>::FindNotChar(StrA, nullptr, LITERAL(T, "H J"), nullptr, ESearchDirection::FromEnd) == StrA + 3);
always_check(TCString<T>::FindNotChar(StrA, BUFFER_SIZE, LITERAL(T, "H J"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 3); always_check(TCString<T>::FindNotChar(StrA, EndA , LITERAL(T, "H J"), nullptr, ESearchDirection::FromEnd) == StrA + 3);
always_check(TCString<T>::Copy(StrA, IGNORE_SIZE, LITERAL(T, "01234567890123456789"), IGNORE_SIZE) != nullptr); always_check(TCString<T>::Copy(StrA, nullptr, LITERAL(T, "01234567890123456789"), nullptr) != nullptr);
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE) == StrA); always_check(TCString<T>::FindString(StrA, nullptr, LITERAL(T, ""), nullptr) == StrA);
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, ""), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 20); always_check(TCString<T>::FindString(StrA, nullptr, LITERAL(T, ""), nullptr, ESearchDirection::FromEnd) == StrA + 20);
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "345"), IGNORE_SIZE) == StrA + 3); always_check(TCString<T>::FindString(StrA, nullptr, LITERAL(T, "345"), nullptr) == StrA + 3);
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "345"), IGNORE_SIZE, ESearchDirection::FromEnd) == StrA + 13); always_check(TCString<T>::FindString(StrA, nullptr, LITERAL(T, "345"), nullptr, ESearchDirection::FromEnd) == StrA + 13);
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "012345678901234567890123456789"), IGNORE_SIZE) == nullptr); always_check(TCString<T>::FindString(StrA, nullptr, LITERAL(T, "012345678901234567890123456789"), nullptr) == nullptr);
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "012345678901234567890123456789"), IGNORE_SIZE, ESearchDirection::FromEnd) == nullptr); always_check(TCString<T>::FindString(StrA, nullptr, LITERAL(T, "012345678901234567890123456789"), nullptr, ESearchDirection::FromEnd) == nullptr);
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "ABC"), IGNORE_SIZE) == nullptr); always_check(TCString<T>::FindString(StrA, nullptr, LITERAL(T, "ABC"), nullptr) == nullptr);
always_check(TCString<T>::FindString(StrA, IGNORE_SIZE, LITERAL(T, "ABC"), IGNORE_SIZE, ESearchDirection::FromEnd) == nullptr); always_check(TCString<T>::FindString(StrA, nullptr, LITERAL(T, "ABC"), nullptr, ESearchDirection::FromEnd) == nullptr);
}; };
TestTCString(InPlaceType<char>); TestTCString(InPlaceType<char>);

View File

@ -18,9 +18,6 @@ NAMESPACE_MODULE_BEGIN(Utility)
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 4996) #pragma warning(disable : 4996)
/** Explicit instructions to ignore buffer size, but may lead to buffer overflow attacks. */
constexpr size_t IGNORE_SIZE = -1;
/** Determines search direction for string operations. */ /** Determines search direction for string operations. */
enum class ESearchDirection enum class ESearchDirection
{ {
@ -37,14 +34,12 @@ struct TCString
{ {
using CharType = T; using CharType = T;
/** Copies one string to another. The size is used only for buffer safety and will not append null characters to the destination. */ /** Copies one string to another. The end sentinel is used only for buffer safety and will not append null characters to the destination. */
FORCEINLINE static CharType* Copy(CharType* Destination, size_t DestinationSize, const CharType* Source, size_t SourceSize) FORCEINLINE static CharType* Copy(CharType* Destination, const CharType* DestinationEnd, const CharType* Source, const CharType* SourceEnd)
{ {
checkf(Destination && Source, TEXT("Read access violation. Destination and source must not be nullptr.")); checkf(Destination && Source, TEXT("Read access violation. Destination and source must not be nullptr."));
checkf(DestinationSize != 0 && SourceSize != 0, TEXT("Illegal buffer size. DestinationSize and SourceSize must not be zero.")); if (DestinationEnd == nullptr && SourceEnd == nullptr)
if (DestinationSize == IGNORE_SIZE && SourceSize == IGNORE_SIZE)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -56,9 +51,9 @@ struct TCString
} }
} }
size_t SourceLength = TCString::Length(Source, SourceSize); size_t SourceLength = TCString::Length(Source, SourceEnd);
if (DestinationSize != IGNORE_SIZE && DestinationSize < SourceLength + 1) if (DestinationEnd != nullptr && Destination + SourceLength + 1 > DestinationEnd)
{ {
return nullptr; return nullptr;
} }
@ -70,14 +65,12 @@ struct TCString
return Destination; return Destination;
} }
/** Concatenates two strings. The size is used only for buffer safety and will not append null characters to the destination. */ /** Concatenates two strings. The end sentinel is used only for buffer safety and will not append null characters to the destination. */
FORCEINLINE static CharType* Cat(CharType* Destination, size_t DestinationSize, const CharType* Source, size_t SourceSize) FORCEINLINE static CharType* Cat(CharType* Destination, const CharType* DestinationEnd, const CharType* Source, const CharType* SourceEnd)
{ {
checkf(Destination && Source, TEXT("Read access violation. Destination and source must not be nullptr.")); checkf(Destination && Source, TEXT("Read access violation. Destination and source must not be nullptr."));
checkf(DestinationSize != 0 && SourceSize != 0, TEXT("Illegal buffer size. DestinationSize and SourceSize must not be zero.")); if (DestinationEnd == nullptr && SourceEnd == nullptr)
if (DestinationSize == IGNORE_SIZE && SourceSize == IGNORE_SIZE)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -89,21 +82,19 @@ struct TCString
} }
} }
size_t DestinationLength = TCString::Length(Destination, DestinationSize); size_t DestinationLength = TCString::Length(Destination, DestinationEnd);
CharType* Result = Copy(Destination + DestinationLength, DestinationSize - DestinationLength, Source, SourceSize); CharType* Result = Copy(Destination + DestinationLength, DestinationEnd, Source, SourceEnd);
return Result ? Destination : nullptr; return Result ? Destination : nullptr;
} }
/** @return The length of a given string. The maximum length is the buffer size. */ /** @return The length of a given string. The maximum length is the buffer size. */
NODISCARD FORCEINLINE static size_t Length(const CharType* InString, size_t SourceSize) NODISCARD FORCEINLINE static size_t Length(const CharType* InString, const CharType* End)
{ {
checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); checkf(InString, TEXT("Read access violation. InString must not be nullptr."));
checkf(SourceSize != 0, TEXT("Illegal buffer size. SourceSize must not be zero.")); if (End == nullptr)
if (SourceSize == IGNORE_SIZE)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -117,24 +108,21 @@ struct TCString
size_t Result = 0; size_t Result = 0;
while (*InString != LITERAL(CharType, '\0') && SourceSize != 0) while (*InString != LITERAL(CharType, '\0') && InString != End)
{ {
++Result; ++Result;
++InString; ++InString;
--SourceSize;
} }
return Result; return Result;
} }
/** Compares two strings. The size is used only for buffer safety not for comparison. */ /** Compares two strings. The end sentinel is used only for buffer safety not for comparison. */
NODISCARD FORCEINLINE static strong_ordering Compare(const CharType* LHS, size_t LHSSize, const CharType* RHS, size_t RHSSize) NODISCARD FORCEINLINE static strong_ordering Compare(const CharType* LHS, const CharType* LHSEnd, const CharType* RHS, const CharType* RHSEnd)
{ {
checkf(LHS && RHS, TEXT("Read access violation. LHS and RHS must not be nullptr.")); checkf(LHS && RHS, TEXT("Read access violation. LHS and RHS must not be nullptr."));
checkf(LHSSize != 0 && RHSSize != 0, TEXT("Illegal buffer size. LHSSize and RHSSize must not be zero.")); if (LHSEnd == nullptr && RHSEnd == nullptr)
if (LHSSize == IGNORE_SIZE && RHSSize == IGNORE_SIZE)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -146,7 +134,7 @@ struct TCString
} }
} }
while (LHSSize != 0 && RHSSize != 0) while (LHS != LHSEnd && RHS != RHSEnd)
{ {
if (*LHS != *RHS) if (*LHS != *RHS)
{ {
@ -160,24 +148,29 @@ struct TCString
++LHS; ++LHS;
++RHS; ++RHS;
--LHSSize;
--RHSSize;
} }
return LHSSize <=> RHSSize; if (LHS != LHSEnd && RHS == RHSEnd)
{
return *LHS <=> LITERAL(CharType, '\0');
}
else if (LHS == LHSEnd && RHS != RHSEnd)
{
return LITERAL(CharType, '\0') <=> *RHS;
} }
/** Finds the first or last occurrence of a character that satisfies the predicate. The terminating null character is considered to be a part of the string. The size is used only for buffer safety. */ return strong_ordering::equal;
}
/** Finds the first or last occurrence of a character that satisfies the predicate. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */
template <CPredicate<CharType> F> template <CPredicate<CharType> F>
NODISCARD FORCEINLINE static const CharType* Find(const CharType* InString, size_t BufferSize, F&& InPredicate, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static const CharType* Find(const CharType* InString, const CharType* End, F&& InPredicate, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); checkf(InString, TEXT("Read access violation. InString must not be nullptr."));
checkf(BufferSize != 0, TEXT("Illegal buffer size. BufferSize must not be zero."));
if (SearchDirection == ESearchDirection::FromStart) if (SearchDirection == ESearchDirection::FromStart)
{ {
while (BufferSize != 0) while (InString != End)
{ {
if (InvokeResult<bool>(Forward<F>(InPredicate), *InString)) if (InvokeResult<bool>(Forward<F>(InPredicate), *InString))
{ {
@ -187,52 +180,47 @@ struct TCString
if (*InString == LITERAL(CharType, '\0')) break; if (*InString == LITERAL(CharType, '\0')) break;
++InString; ++InString;
--BufferSize;
} }
} }
else else
{ {
size_t Index = TCString::Length(InString, BufferSize); size_t Index = TCString::Length(InString, End);
if (Index == BufferSize) --Index; const CharType* Iter = InString + Index;
while (true) if (Iter == End) --Iter;
while (Iter != InString - 1)
{ {
if (InvokeResult<bool>(Forward<F>(InPredicate), InString[Index])) if (InvokeResult<bool>(Forward<F>(InPredicate), *Iter))
{ {
return InString + Index; return Iter;
} }
if (Index == 0) break; --Iter;
--Index;
} }
} }
return nullptr; return nullptr;
} }
/** Finds the first or last occurrence of a character that satisfies the predicate. The terminating null character is considered to be a part of the string. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character that satisfies the predicate. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */
template <CPredicate<CharType> F> template <CPredicate<CharType> F>
NODISCARD FORCEINLINE static CharType* Find( CharType* InString, size_t BufferSize, F&& InPredicate, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static CharType* Find( CharType* InString, const CharType* End, F&& InPredicate, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); checkf(InString, TEXT("Read access violation. InString must not be nullptr."));
checkf(BufferSize != 0, TEXT("Illegal buffer size. BufferSize must not be zero."));
check_no_recursion(); check_no_recursion();
return const_cast<CharType*>(TCString::Find(const_cast<const CharType*>(InString), BufferSize, Forward<F>(InPredicate), SearchDirection)); return const_cast<CharType*>(TCString::Find(const_cast<const CharType*>(InString), End, Forward<F>(InPredicate), SearchDirection));
} }
/** Finds the first or last occurrence of a character. The terminating null character is considered to be a part of the string. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static const CharType* FindChar(const CharType* InString, size_t BufferSize, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static const CharType* FindChar(const CharType* InString, const CharType* End, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); checkf(InString, TEXT("Read access violation. InString must not be nullptr."));
checkf(BufferSize != 0, TEXT("Illegal buffer size. BufferSize must not be zero.")); if (End == nullptr)
if (BufferSize == IGNORE_SIZE)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -244,29 +232,25 @@ struct TCString
} }
} }
return TCString::Find(InString, BufferSize, [Character](CharType C) { return C == Character; }, SearchDirection); return TCString::Find(InString, End, [Character](CharType C) { return C == Character; }, SearchDirection);
} }
/** Finds the first or last occurrence of a character. The terminating null character is considered to be a part of the string. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static CharType* FindChar( CharType* InString, size_t BufferSize, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static CharType* FindChar( CharType* InString, const CharType* End, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); checkf(InString, TEXT("Read access violation. InString must not be nullptr."));
checkf(BufferSize != 0, TEXT("Illegal buffer size. BufferSize must not be zero."));
check_no_recursion(); check_no_recursion();
return const_cast<CharType*>(TCString::FindChar(const_cast<const CharType*>(InString), BufferSize, Character, SearchDirection)); return const_cast<CharType*>(TCString::FindChar(const_cast<const CharType*>(InString), End, Character, SearchDirection));
} }
/** Finds the first or last occurrence of a character in a charset. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character in a charset. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static const CharType* FindChar(const CharType* InString, size_t BufferSize, const CharType* Charset, size_t CharsetSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static const CharType* FindChar(const CharType* InString, const CharType* End, const CharType* Charset, const CharType* CharsetEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr.")); checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr."));
checkf(BufferSize != 0 && CharsetSize != 0, TEXT("Illegal buffer size. BufferSize and CharsetSize must not be zero.")); if (End == nullptr && CharsetEnd == nullptr && SearchDirection == ESearchDirection::FromStart)
if (BufferSize == IGNORE_SIZE && CharsetSize == IGNORE_SIZE && SearchDirection == ESearchDirection::FromStart)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -280,41 +264,39 @@ struct TCString
return TCString::Find return TCString::Find
( (
InString, BufferSize, InString, End,
[Charset, CharsetSize](CharType C) [Charset, CharsetEnd](CharType C)
{ {
const CharType* Result = TCString::FindChar(Charset, CharsetSize, C); const CharType* Result = TCString::FindChar(Charset, CharsetEnd, C);
return Result != nullptr && *Result != LITERAL(CharType, '\0'); return Result != nullptr && *Result != LITERAL(CharType, '\0');
}, },
SearchDirection SearchDirection
); );
} }
/** Finds the first or last occurrence of a character in a charset. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character in a charset. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static CharType* FindChar( CharType* InString, size_t BufferSize, const CharType* Charset, size_t CharsetSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static CharType* FindChar( CharType* InString, const CharType* End, const CharType* Charset, const CharType* CharsetEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr.")); checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr."));
checkf(BufferSize != 0 && CharsetSize != 0, TEXT("Illegal buffer size. BufferSize and CharsetSize must not be zero."));
check_no_recursion(); check_no_recursion();
return const_cast<CharType*>(TCString::FindChar(const_cast<const CharType*>(InString), BufferSize, Charset, CharsetSize, SearchDirection)); return const_cast<CharType*>(TCString::FindChar(const_cast<const CharType*>(InString), End, Charset, CharsetEnd, SearchDirection));
} }
/** Finds the first or last occurrence of a character that is not the given character. The terminating null character is considered to be a part of the string. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character that is not the given character. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static const CharType* FindNotChar(const CharType* InString, size_t BufferSize, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static const CharType* FindNotChar(const CharType* InString, const CharType* End, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); checkf(InString, TEXT("Read access violation. InString must not be nullptr."));
checkf(BufferSize != 0, TEXT("Illegal buffer size. BufferSize must not be zero.")); if (InString == End) return nullptr;
if (Character == LITERAL(CharType, '\0') && SearchDirection == ESearchDirection::FromStart) if (Character == LITERAL(CharType, '\0') && SearchDirection == ESearchDirection::FromStart)
{ {
return *InString != LITERAL(CharType, '\0') ? InString : nullptr; return *InString != LITERAL(CharType, '\0') ? InString : nullptr;
} }
if (BufferSize == IGNORE_SIZE && SearchDirection == ESearchDirection::FromStart) if (End == nullptr && SearchDirection == ESearchDirection::FromStart)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -328,29 +310,25 @@ struct TCString
} }
} }
return TCString::Find(InString, BufferSize, [Character](CharType C) { return C != Character; }, SearchDirection); return TCString::Find(InString, End, [Character](CharType C) { return C != Character; }, SearchDirection);
} }
/** Finds the first or last occurrence of a character that is not the given character. The terminating null character is considered to be a part of the string. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character that is not the given character. The terminating null character is considered to be a part of the string. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static CharType* FindNotChar( CharType* InString, size_t BufferSize, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static CharType* FindNotChar( CharType* InString, const CharType* End, CharType Character, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString, TEXT("Read access violation. InString must not be nullptr.")); checkf(InString, TEXT("Read access violation. InString must not be nullptr."));
checkf(BufferSize != 0, TEXT("Illegal buffer size. BufferSize must not be zero."));
check_no_recursion(); check_no_recursion();
return const_cast<CharType*>(TCString::FindNotChar(const_cast<const CharType*>(InString), BufferSize, Character, SearchDirection)); return const_cast<CharType*>(TCString::FindNotChar(const_cast<const CharType*>(InString), End, Character, SearchDirection));
} }
/** Finds the first or last occurrence of a character that is not in the given charset. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character that is not in the given charset. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static const CharType* FindNotChar(const CharType* InString, size_t BufferSize, const CharType* Charset, size_t CharsetSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static const CharType* FindNotChar(const CharType* InString, const CharType* End, const CharType* Charset, const CharType* CharsetEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr.")); checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr."));
checkf(BufferSize != 0 && CharsetSize != 0, TEXT("Illegal buffer size. BufferSize and CharsetSize must not be zero.")); if (End == nullptr && CharsetEnd == nullptr && SearchDirection == ESearchDirection::FromStart)
if (BufferSize == IGNORE_SIZE && CharsetSize == IGNORE_SIZE && SearchDirection == ESearchDirection::FromStart)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -364,34 +342,38 @@ struct TCString
} }
} }
return TCString::Find(InString, BufferSize, [Charset, CharsetSize](CharType C) { return TCString::FindChar(Charset, CharsetSize, C) == nullptr; }, SearchDirection); return TCString::Find(InString, End, [Charset, CharsetEnd](CharType C) { return TCString::FindChar(Charset, CharsetEnd, C) == nullptr; }, SearchDirection);
} }
/** Finds the first or last occurrence of a character that is not in the given charset. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a character that is not in the given charset. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static CharType* FindNotChar( CharType* InString, size_t BufferSize, const CharType* Charset, size_t CharsetSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static CharType* FindNotChar( CharType* InString, const CharType* End, const CharType* Charset, const CharType* CharsetEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr.")); checkf(InString && Charset, TEXT("Read access violation. InString and Charset must not be nullptr."));
checkf(BufferSize != 0 && CharsetSize != 0, TEXT("Illegal buffer size. BufferSize and CharsetSize must not be zero."));
check_no_recursion(); check_no_recursion();
return const_cast<CharType*>(TCString::FindNotChar(const_cast<const CharType*>(InString), BufferSize, Charset, CharsetSize, SearchDirection)); return const_cast<CharType*>(TCString::FindNotChar(const_cast<const CharType*>(InString), End, Charset, CharsetEnd, SearchDirection));
} }
/** Finds the first or last occurrence of a substring. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a substring. The end sentinel is used only for buffer safety. */
NODISCARD static const CharType* FindString(const CharType* InString, size_t BufferSize, const CharType* Substring, size_t SubstringSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD static const CharType* FindString(const CharType* InString, const CharType* End, const CharType* Substring, const CharType* SubstringEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString && Substring, TEXT("Read access violation. InString and Substring must not be nullptr.")); checkf(InString && Substring, TEXT("Read access violation. InString and Substring must not be nullptr."));
checkf(BufferSize != 0 && SubstringSize != 0, TEXT("Illegal buffer size. BufferSize and SubstringSize must not be zero.")); if (InString == End) return nullptr;
if (*Substring == LITERAL(CharType, '\0')) if (Substring == SubstringEnd || *Substring == LITERAL(CharType, '\0'))
{ {
return SearchDirection == ESearchDirection::FromStart ? InString : InString + TCString::Length(InString, BufferSize); if (SearchDirection == ESearchDirection::FromStart) return InString;
else
{
const CharType* Iter = InString + TCString::Length(InString, End);
if (Iter == End) --Iter;
return Iter;
}
} }
if (BufferSize == IGNORE_SIZE && SubstringSize == IGNORE_SIZE && SearchDirection == ESearchDirection::FromStart) if (End == nullptr && SubstringEnd == nullptr && SearchDirection == ESearchDirection::FromStart)
{ {
if constexpr (CSameAs<CharType, char>) if constexpr (CSameAs<CharType, char>)
{ {
@ -403,8 +385,8 @@ struct TCString
} }
} }
size_t StringLength = TCString::Length(InString, BufferSize); size_t StringLength = TCString::Length(InString, End);
size_t SubstringLength = TCString::Length(Substring, SubstringSize); size_t SubstringLength = TCString::Length(Substring, SubstringEnd);
if (StringLength < SubstringLength) if (StringLength < SubstringLength)
{ {
@ -415,7 +397,7 @@ struct TCString
{ {
for (size_t Index = 0; Index < StringLength - SubstringLength; ++Index) for (size_t Index = 0; Index < StringLength - SubstringLength; ++Index)
{ {
if (TCString::Compare(InString + Index, SubstringLength, Substring, SubstringLength) == 0) if (TCString::Compare(InString + Index, InString + Index + SubstringLength, Substring, Substring + SubstringLength) == 0)
{ {
return InString + Index; return InString + Index;
} }
@ -425,7 +407,7 @@ struct TCString
{ {
for (size_t Index = StringLength - SubstringLength; Index > 0; --Index) for (size_t Index = StringLength - SubstringLength; Index > 0; --Index)
{ {
if (TCString::Compare(InString + Index, SubstringLength, Substring, SubstringLength) == 0) if (TCString::Compare(InString + Index, InString + Index + SubstringLength, Substring, Substring + SubstringLength) == 0)
{ {
return InString + Index; return InString + Index;
} }
@ -435,16 +417,14 @@ struct TCString
return nullptr; return nullptr;
} }
/** Finds the first or last occurrence of a substring. The size is used only for buffer safety. */ /** Finds the first or last occurrence of a substring. The end sentinel is used only for buffer safety. */
NODISCARD FORCEINLINE static CharType* FindString( CharType* InString, size_t BufferSize, const CharType* Substring, size_t SubstringSize, ESearchDirection SearchDirection = ESearchDirection::FromStart) NODISCARD FORCEINLINE static CharType* FindString( CharType* InString, const CharType* End, const CharType* Substring, const CharType* SubstringEnd, ESearchDirection SearchDirection = ESearchDirection::FromStart)
{ {
checkf(InString && Substring, TEXT("Read access violation. InString and Substring must not be nullptr.")); checkf(InString && Substring, TEXT("Read access violation. InString and Substring must not be nullptr."));
checkf(BufferSize != 0 && SubstringSize != 0, TEXT("Illegal buffer size. BufferSize and SubstringSize must not be zero."));
check_no_recursion(); check_no_recursion();
return const_cast<CharType*>(TCString::FindString(const_cast<const CharType*>(InString), BufferSize, Substring, SubstringSize, SearchDirection)); return const_cast<CharType*>(TCString::FindString(const_cast<const CharType*>(InString), End, Substring, SubstringEnd, SearchDirection));
} }
}; };