Last active
June 12, 2023 04:20
-
-
Save udaken/69c4c51d7e6c1a63c4b0aea431c10d16 to your computer and use it in GitHub Desktop.
Tiny String class for Win32.(licensed under The Unlicense)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <windows.h> | |
#include <Shlwapi.h> | |
#include <Strsafe.h> | |
#pragma comment(lib, "Shlwapi.lib") | |
#include <utility> | |
#include <cassert> | |
#include <cwchar> | |
#include <cstdint> | |
#if _MSC_VER < 1900 | |
#ifndef noexcept | |
#define noexcept throw() | |
#endif | |
#endif | |
#if __cpp_rvalue_references >= 200610 | |
#include <memory> | |
#define TWSTRING_MOVE(...) std::move(__VA_ARGS__) | |
#else | |
#define TWSTRING_MOVE(...) (__VA_ARGS__) | |
#endif | |
#ifdef TWSTRING_NAMESPACE | |
namespace TWSTRING_NAMESPACE { | |
#endif | |
#define TWSTRING_CHECK ::DebugBreak() | |
#if !NDEBUG | |
#define TWSTRING_HEAP_FLAG HEAP_GENERATE_EXCEPTIONS | |
#else | |
#define TWSTRING_HEAP_FLAG 0 | |
#endif | |
template <class TChar> | |
struct TinyWinStringAllocator | |
{ | |
typedef TChar* Ptr; | |
static Ptr const NullValue; | |
TinyWinStringAllocator() | |
: hHeap(::GetProcessHeap()) | |
{ | |
} | |
explicit TinyWinStringAllocator(HANDLE h) | |
: hHeap(h) | |
{ | |
} | |
inline Ptr Alloc(SIZE_T size) noexcept | |
{ | |
if (size > INT_MAX) | |
{ | |
TWSTRING_CHECK; | |
return NullValue; | |
} | |
Ptr newPtr = reinterpret_cast<TChar*>(::HeapAlloc(hHeap, HEAP_ZERO_MEMORY | TWSTRING_HEAP_FLAG, size * sizeof(TChar))); | |
return newPtr; | |
} | |
inline Ptr ReAlloc(Ptr p, SIZE_T size) noexcept | |
{ | |
if (size > INT_MAX) | |
{ | |
TWSTRING_CHECK; | |
return NullValue; | |
} | |
Ptr newPtr = reinterpret_cast<TChar*>(::HeapReAlloc(hHeap, HEAP_ZERO_MEMORY | TWSTRING_HEAP_FLAG, p, size * sizeof(TChar))); | |
if (!newPtr) | |
TWSTRING_CHECK; | |
return newPtr; | |
} | |
inline void Free(Ptr p) noexcept | |
{ | |
if (!::HeapFree(hHeap, 0, p)) | |
TWSTRING_CHECK; | |
} | |
TChar* CastTo(Ptr from) const noexcept { return reinterpret_cast<TChar*>(from); } | |
private: | |
HANDLE hHeap; | |
}; | |
template <class TChar> | |
typename TinyWinStringAllocator<TChar>::Ptr const TinyWinStringAllocator<TChar>::NullValue = NULL; | |
template <class Allocator = TinyWinStringAllocator<TCHAR> > | |
class TinyWinString | |
{ | |
enum { DefaultBufferLength = 16 }; | |
static const size_t BufferGrows = 16; | |
//typedef typename Allocator::Ptr Ptr; | |
private: | |
// internal | |
explicit TinyWinString(Allocator& allocator, typename Allocator::Ptr ptr, SIZE_T capacity) | |
: m_allocator(allocator) | |
, m_buffer(ptr) | |
, m_capacity(capacity) | |
{ | |
} | |
inline static TCHAR* strmove(LPTSTR dest, LPCTSTR src, SIZE_T size) | |
{ | |
#if UNICODE | |
wmemmove(dest, src, size); | |
#else | |
memmove(dest, src, size); | |
#endif | |
return dest; | |
} | |
TinyWinString& append(LPCTSTR other, SIZE_T nLength) | |
{ | |
SIZE_T newLength = GetLength() + nLength; | |
if (newLength + 1 > GetAllocLength()) | |
{ | |
Preallocate(newLength + 1); | |
} | |
StrNCat(GetBuffer(), other, nLength + 1); | |
return *this; | |
} | |
public: | |
explicit TinyWinString(SIZE_T allocLength = 0, Allocator allocator = Allocator()) | |
: m_allocator(allocator) | |
, m_buffer(allocLength > 0 ? allocator.Alloc(allocLength) : allocator.NullValue) | |
, m_capacity(allocLength) | |
{ | |
} | |
template <class A> | |
TinyWinString(const TinyWinString<A>& other) | |
: m_allocator(other.m_allocator) | |
, m_buffer(m_allocator.Alloc(other.m_capacity)) | |
, m_capacity(other.m_capacity) | |
{ | |
other.CopyTo(GetBuffer(), m_capacity); | |
} | |
explicit TinyWinString(LPCTSTR pchSrc, SIZE_T nLength, Allocator allocator = Allocator()) | |
: TinyWinString(nLength + 1, allocator) | |
{ | |
assert(nLength >= 0); | |
CopyChars(GetBuffer(), pchSrc, m_capacity); | |
} | |
template <SIZE_T nLength> | |
constexpr TinyWinString(const TCHAR(&other)[nLength], Allocator allocator = Allocator()) | |
: m_allocator(allocator) | |
, m_buffer(m_allocator.Alloc(nLength)) | |
, m_capacity(nLength) | |
{ | |
strmove(m_buffer, other, m_capacity); | |
} | |
TinyWinString(LPCTSTR other, Allocator allocator = Allocator()) | |
: TinyWinString(other, StringLength(other), allocator) | |
{ | |
} | |
#ifdef UNICODE | |
explicit TinyWinString(LPCSTR other, int nLength = -1, DWORD codepage = CP_THREAD_ACP, DWORD dwFlags = 0, Allocator allocator = Allocator()) | |
: TinyWinString(static_cast<SIZE_T>(::MultiByteToWideChar(codepage, dwFlags, other, nLength, NULL, 0)), allocator) | |
{ | |
if (!::MultiByteToWideChar(codepage, dwFlags, other, nLength, GetBuffer(), static_cast<int>(m_capacity))) | |
TWSTRING_CHECK; | |
} | |
#endif | |
#if __cpp_rvalue_references >= 200610 | |
TinyWinString(TinyWinString&& other) noexcept | |
: TinyWinString(other.m_allocator, other.m_buffer, other.m_capacity) | |
{ | |
other.m_buffer = other.m_allocator.NullValue; | |
other.m_capacity = 0; | |
} | |
#endif | |
~TinyWinString() | |
{ | |
Empty(); | |
} | |
#pragma region SimpleString | |
template <class A> | |
TinyWinString& Append(const TinyWinString<A>& other) | |
{ | |
return Append(other.m_buffer); | |
} | |
TinyWinString& Append(LPCTSTR other) | |
{ | |
return append(other, StringLength(other)); | |
} | |
template <SIZE_T nLength> | |
TinyWinString& Append(const TCHAR(&other)[nLength]) | |
{ | |
return Append(other, nLength); | |
} | |
TinyWinString& Append(LPCTSTR other, SIZE_T nLength) | |
{ | |
if (nLength > StringLength(other)) | |
TWSTRING_CHECK; | |
return append(other, nLength); | |
} | |
TinyWinString& AppendChar(TCHAR c) | |
{ | |
return append(&c, 1); | |
} | |
static void CopyChars( | |
LPTSTR pchDest, | |
LPCTSTR pchSrc, | |
SIZE_T nChars) noexcept | |
{ | |
assert(nChars <= MAXINT); | |
lstrcpyn(pchDest, pchSrc, static_cast<int>(nChars)); | |
pchDest[nChars - 1] = TEXT('\0'); | |
} | |
void Empty() noexcept | |
{ | |
if (m_buffer != m_allocator.NullValue) | |
{ | |
m_allocator.Free(m_buffer); | |
m_buffer = m_allocator.NullValue; | |
m_capacity = 0; | |
} | |
} | |
SIZE_T GetAllocLength() const { return m_capacity; } | |
const TCHAR& GetAt(SIZE_T iChar) const | |
{ | |
if (iChar > GetLength()) | |
TWSTRING_CHECK; | |
return GetBuffer()[iChar]; | |
} | |
__forceinline LPTSTR GetBuffer() { return m_allocator.CastTo(m_buffer); } | |
__forceinline LPCTSTR GetBuffer() const { return m_allocator.CastTo(m_buffer); } | |
Allocator& GetAllocator() { return m_allocator; } | |
size_t GetLength() const | |
{ | |
return m_buffer != m_allocator.NullValue ? StringLength(GetBuffer()) : 0; | |
} | |
LPCTSTR GetString() const noexcept | |
{ | |
if (m_buffer != m_allocator.NullValue) return GetBuffer(); | |
else return TEXT(""); | |
} | |
bool IsEmpty() const noexcept | |
{ | |
return m_buffer == m_allocator.NullValue; | |
} | |
void Preallocate(SIZE_T nLength) | |
{ | |
typename Allocator::Ptr newPtr; | |
if (m_buffer != m_allocator.NullValue) | |
{ | |
newPtr = m_allocator.ReAlloc(m_buffer, nLength); | |
} | |
else | |
{ | |
newPtr = m_allocator.Alloc(nLength); | |
} | |
if (newPtr != m_allocator.NullValue) | |
{ | |
m_buffer = newPtr; | |
m_capacity = nLength; | |
} | |
else | |
{ | |
TWSTRING_CHECK; | |
} | |
} | |
void SetAt(SIZE_T iChar, TCHAR ch) | |
{ | |
if (iChar > GetLength()) | |
{ | |
TWSTRING_CHECK; | |
return; | |
} | |
GetBuffer()[iChar] = ch; | |
} | |
void SetString(LPCTSTR pszSrc, SIZE_T nLength) | |
{ | |
if (nLength + 1 > GetAllocLength()) | |
{ | |
Preallocate(nLength + 1); | |
} | |
CopyChars(GetBuffer(), pszSrc, nLength); | |
} | |
void SetString(LPCTSTR pszSrc) | |
{ | |
SetString(pszSrc, StringLength(pszSrc)); | |
} | |
static SIZE_T StringLength(LPCTSTR psz) noexcept | |
{ | |
return lstrlen(psz); | |
} | |
void Truncate(int nNewLength) | |
{ | |
SetAt(nNewLength, TEXT('\0')); | |
} | |
const TCHAR& operator[](SIZE_T iChar) const | |
{ | |
return GetAt(iChar); | |
} | |
operator LPCTSTR() const noexcept | |
{ | |
return GetString(); | |
} | |
#pragma endregion | |
void __cdecl AppendFormat(LPCTSTR pszFormat, ...) | |
{ | |
va_list ap; | |
va_start(ap, pszFormat); | |
TinyWinString temp; | |
temp.FormatV(pszFormat, ap); | |
Append(temp); | |
} | |
enum CompareFlag | |
{ | |
CompareFlagNone = 0, | |
LinguisticIgnoreCase = LINGUISTIC_IGNORECASE, | |
LinguisticIgnoreDiacritic = LINGUISTIC_IGNOREDIACRITIC, | |
NormIgnoreCase = NORM_IGNORECASE, | |
NormIgnoreKanaType = NORM_IGNOREKANATYPE, | |
NormIgnoreNonSpace = NORM_IGNORENONSPACE, | |
NormIgnoreSymbols = NORM_IGNORESYMBOLS, | |
NormIgnoreWidth = NORM_IGNOREWIDTH, | |
NormLinguisticCasing = NORM_LINGUISTIC_CASING, | |
#if (WINVER >= 0x0601) | |
SortDigitsasNumbbers = SORT_DIGITSASNUMBERS, | |
#endif | |
SortStringSort = SORT_STRINGSORT, | |
}; | |
int Collate(LPCTSTR other, CompareFlag dwCmpFlags = CompareFlagNone) const | |
{ | |
return Collate(other, ::GetThreadLocale(), dwCmpFlags); | |
} | |
int Collate(LPCTSTR other, LCID locale, CompareFlag dwCmpFlags = CompareFlagNone) const | |
{ | |
switch (::CompareString(locale, dwCmpFlags, GetBuffer(), -1, other, -1)) | |
{ | |
case CSTR_LESS_THAN: return -1; | |
case CSTR_EQUAL: return 0; | |
case CSTR_GREATER_THAN: return 1; | |
default: TWSTRING_CHECK; | |
} | |
} | |
int CollateNoCase(LPCTSTR other) const | |
{ | |
return Collate(other, ::GetThreadLocale(), NormIgnoreCase); | |
} | |
int CollateNoCase(LPCTSTR other, LCID locale) const | |
{ | |
return Collate(other, locale, NormIgnoreCase); | |
} | |
int Compare(LPCTSTR other) const | |
{ | |
return lstrcmp(GetString(), other); | |
} | |
int CompareNoCase(LPCTSTR other) const | |
{ | |
return lstrcmpi(GetString(), other); | |
} | |
template <size_t cch> | |
void CopyTo(TCHAR(&buffer)) const | |
{ | |
CopyTo(buffer, cch); | |
} | |
void CopyTo(LPTSTR buffer, SIZE_T cch) const | |
{ | |
CopyChars(buffer, GetString(), static_cast<int>(cch)); | |
} | |
#ifdef UNICODE | |
template <size_t cch> | |
int CopyTo(CHAR(&buffer)[cch], UINT cp = CP_THREAD_ACP) const | |
{ | |
return CopyTo(buffer, cch, cp); | |
} | |
int CopyTo(LPSTR buffer, SIZE_T cch, UINT cp = CP_THREAD_ACP) const | |
{ | |
return ::WideCharToMultiByte(cp, 0, GetBuffer(), -1, buffer, cch, NULL, NULL); | |
} | |
#endif | |
ptrdiff_t Find(LPCTSTR pszSub, SIZE_T iStart = 0) const noexcept | |
{ | |
LPCTSTR n = StrStr(GetString() + iStart, pszSub); | |
return n == NULL ? -1 : n - GetString(); | |
} | |
ptrdiff_t Find(TCHAR ch, SIZE_T iStart = 0) const noexcept | |
{ | |
LPCTSTR n = StrChr(GetString() + iStart, ch); | |
return n == NULL ? -1 : n - GetString(); | |
} | |
void __cdecl Format(LPCTSTR pszFormat, ...) | |
{ | |
va_list ap; | |
va_start(ap, pszFormat); | |
FormatV(pszFormat, ap); | |
} | |
__forceinline void __cdecl FormatV(LPCTSTR pszFormat, va_list args) | |
{ | |
#if TWSTRING_USE_SAFESTR | |
HRESULT hr; | |
do | |
{ | |
hr = ::StringCchVPrintf(GetBuffer(), m_capacity, pszFormat, args); | |
if (hr == STRSAFE_E_INSUFFICIENT_BUFFER && m_capacity < (STRSAFE_MAX_CCH - BufferGrows)) | |
{ | |
Preallocate(m_capacity + BufferGrows); | |
} | |
else | |
{ | |
break; | |
} | |
} while (true); | |
#else | |
do { | |
SIZE_T n = wvnsprintf(GetBuffer(), m_capacity, pszFormat, args); | |
if (static_cast<int>(n) < 0) | |
{ | |
Preallocate(m_capacity + DefaultBufferLength); | |
} | |
else if (n < m_capacity) | |
{ | |
SetAt(n, TEXT('\0')); | |
return; | |
} | |
else | |
TWSTRING_CHECK; | |
} while (m_capacity < 1024); | |
TWSTRING_CHECK; | |
#endif | |
} | |
void __cdecl FormatMessage(LPCTSTR pszFormat, ...) | |
{ | |
va_list ap; | |
va_start(ap, pszFormat); | |
FormatMessageV(pszFormat, &ap); | |
} | |
void FormatMessageV(LPCTSTR pszFormat, va_list* pArgList) | |
{ | |
do | |
{ | |
DWORD ret = ::FormatMessage(FORMAT_MESSAGE_FROM_STRING, pszFormat, 0, 0, GetBuffer(), m_capacity, pArgList); | |
if (ret) | |
{ | |
return; | |
} | |
else if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) | |
{ | |
Preallocate(m_capacity + DefaultBufferLength); | |
} | |
} while (m_capacity < 1024); | |
TWSTRING_CHECK; | |
} | |
int Insert(SIZE_T iIndex, LPCTSTR psz) | |
{ | |
SIZE_T currentLength = GetLength(); | |
if (iIndex >= currentLength) | |
TWSTRING_CHECK; | |
SIZE_T optLength = lstrlen(psz); | |
if (m_capacity < currentLength + optLength + 1) | |
Preallocate(currentLength + optLength + 1); | |
strmove(GetBuffer() + iIndex + optLength, GetBuffer() + iIndex, (currentLength - iIndex) + 1); | |
strmove(GetBuffer() + iIndex, psz, optLength); | |
return currentLength + optLength; | |
} | |
TinyWinString Left(SIZE_T nCount) const | |
{ | |
if (nCount > GetLength()) | |
TWSTRING_CHECK; | |
return TWSTRING_MOVE(TinyWinString(GetBuffer(), min(nCount, m_capacity), m_allocator)); | |
} | |
TinyWinString& MakeLower() | |
{ | |
::CharLowerBuff(GetBuffer(), GetLength()); | |
return *this; | |
} | |
// TODO CStringT& MakeReverse(); | |
TinyWinString& MakeUpper() | |
{ | |
::CharUpperBuff(GetBuffer(), GetLength()); | |
return *this; | |
} | |
TinyWinString Mid(SIZE_T iFirst, SIZE_T nCount) const | |
{ | |
if (iFirst + nCount > GetLength()) | |
{ | |
TWSTRING_CHECK; | |
return TinyWinString(); | |
} | |
return TWSTRING_MOVE(TinyWinString(&GetAt(iFirst), nCount, m_allocator)); | |
} | |
TinyWinString Mid(SIZE_T iFirst) const | |
{ | |
if (iFirst > GetLength()) | |
{ | |
TWSTRING_CHECK; | |
return TinyWinString(); | |
} | |
return TWSTRING_MOVE(TinyWinString(&GetAt(iFirst), m_allocator)); | |
} | |
int Remove(TCHAR const chRemove) | |
{ | |
int count = 0; | |
SIZE_T start = 0; | |
ptrdiff_t i; | |
TinyWinString result(GetAllocLength(), m_allocator); | |
while ((i = Find(chRemove)) > 0) | |
{ | |
result.Append(&GetAt(start), i); | |
start = i + 1; | |
i++; | |
count++; | |
} | |
*this = TWSTRING_MOVE(result); | |
return count; | |
} | |
ptrdiff_t ReverseFind(TCHAR ch) const noexcept | |
{ | |
LPCTSTR s = StrRChr(GetBuffer(), GetBuffer() + GetLength(), ch); | |
if (s) | |
return s - GetBuffer(); | |
else | |
return -1; | |
} | |
TinyWinString Right(SIZE_T nCount) const | |
{ | |
SIZE_T len = GetLength(); | |
if (nCount > len) | |
{ | |
TWSTRING_CHECK; | |
return TinyWinString(); | |
} | |
return TWSTRING_MOVE(TinyWinString(GetBuffer() + len - nCount, nCount, m_allocator)); | |
} | |
TinyWinString Tokenize(LPCTSTR pszTokens, INT_PTR& iStart) const | |
{ | |
INT_PTR iStartOrg = iStart; | |
ptrdiff_t pos = Find(pszTokens, iStart); | |
if (pos >= 0) | |
{ | |
iStart = pos + StringLength(pszTokens); | |
return Mid(iStartOrg, iStart - iStartOrg - 1); | |
} | |
else | |
{ | |
iStart = -1; | |
return TWSTRING_MOVE(TinyWinString(TEXT(""))); | |
} | |
} | |
#if __cpp_rvalue_references >= 200610 | |
TinyWinString& operator =(TinyWinString&& other) noexcept | |
{ | |
Empty(); | |
swap(other); | |
return *this; | |
} | |
#endif | |
TinyWinString& operator =(const TinyWinString& other) | |
{ | |
SetString(other.GetString()); | |
return *this; | |
} | |
TinyWinString& operator =(LPCTSTR other) | |
{ | |
SetString(other); | |
return *this; | |
} | |
bool operator ==(LPCTSTR other) const | |
{ | |
return Compare(other) == 0; | |
} | |
template <class A> | |
bool operator ==(const TinyWinString<A>& other) const | |
{ | |
return Compare(other.GetString()) == 0; | |
} | |
bool operator !=(LPCTSTR other) const | |
{ | |
return Compare(other) != 0; | |
} | |
template <class A> | |
bool operator !=(const TinyWinString<A>& other) const | |
{ | |
return Compare(other.GetString()) != 0; | |
} | |
template <class A> | |
friend TinyWinString operator+( | |
const TinyWinString& str1, | |
const TinyWinString<A>& str2) | |
{ | |
TinyWinString temp(str1.GetAllocLength() + str2.GetAllocLength()); | |
temp.SetString(str1.GetString()); | |
temp.Append(str2); | |
return TWSTRING_MOVE(temp); | |
} | |
friend TinyWinString operator+( | |
const TinyWinString& str1, | |
LPCTSTR psz2) | |
{ | |
TinyWinString temp = str1; | |
temp.Append(psz2); | |
return temp; | |
} | |
friend TinyWinString operator+( | |
LPCTSTR psz1, | |
const TinyWinString& str2) | |
{ | |
TinyWinString temp(psz1); | |
temp.Append(str2); | |
return temp; | |
} | |
friend TinyWinString operator+( | |
const TinyWinString& str1, | |
TCHAR ch2) | |
{ | |
TinyWinString temp = str1; | |
temp.AppendChar(ch2); | |
return temp; | |
} | |
friend TinyWinString operator+( | |
TCHAR ch1, | |
const TinyWinString& str2) | |
{ | |
TinyWinString temp(&ch1, 1); | |
temp.Append(str2); | |
return temp; | |
} | |
void DebugPrint() const | |
{ | |
::OutputDebugString(this->GetString()); | |
} | |
static void DebugPrint(LPCTSTR pszFormat, ...) | |
{ | |
va_list ap; | |
va_start(ap, pszFormat); | |
TinyWinString temp; | |
temp.FormatV(pszFormat, ap); | |
::OutputDebugString(temp); | |
} | |
void DebugPrintln() const | |
{ | |
::OutputDebugString((*this + TEXT("\n"))); | |
} | |
static void DebugPrintln(LPCTSTR pszFormat, ...) | |
{ | |
va_list ap; | |
va_start(ap, pszFormat); | |
TinyWinString temp; | |
temp.FormatV(pszFormat, ap); | |
temp.AppendChar(TEXT('\n')); | |
::OutputDebugString(temp); | |
} | |
inline void swap(TinyWinString& other) noexcept | |
{ | |
std::swap(m_buffer, other.m_buffer); | |
std::swap(m_capacity, other.m_capacity); | |
std::swap(m_allocator, other.m_allocator); | |
} | |
typename Allocator::Ptr begin() | |
{ | |
return GetBuffer(); | |
} | |
typename Allocator::Ptr end() | |
{ | |
return GetBuffer() + GetLength() + 1; | |
} | |
const typename Allocator::Ptr begin() const | |
{ | |
return const_cast<TinyWinString*>(this)->begin(); | |
} | |
const typename Allocator::Ptr end() const | |
{ | |
return const_cast<TinyWinString*>(this)->end(); | |
} | |
private: | |
Allocator m_allocator; | |
typename Allocator::Ptr m_buffer; | |
SIZE_T m_capacity; | |
friend class TinyWinStringTest; | |
}; | |
typedef TinyWinString<> TWString; | |
#ifdef TWSTRING_NAMESPACE | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment