diff options
Diffstat (limited to 'zen/string_tools.h')
-rw-r--r-- | zen/string_tools.h | 106 |
1 files changed, 54 insertions, 52 deletions
diff --git a/zen/string_tools.h b/zen/string_tools.h index 2c33a4f8..fc715961 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -16,7 +16,7 @@ #include <vector> #include "stl_tools.h" #include "string_traits.h" -#include "legacy_compiler.h" //<charconv> (without compiler crashes) +#include "legacy_compiler.h" //<charconv> but without the compiler crashes :> //enhance *any* string class with useful non-member functions: @@ -44,12 +44,12 @@ template <class S, class T, typename = std::enable_if_t<IsStringLikeV<S>>> bool template <class S, class T> bool equalString (const S& lhs, const T& rhs); template <class S, class T> bool equalAsciiNoCase(const S& lhs, const T& rhs); - template <class S, class T> int compareString (const S& lhs, const T& rhs); - template <class S, class T> int compareAsciiNoCase(const S& lhs, const T& rhs); //basic case-insensitive comparison (considering A-Z only!) + // template <class S, class T> std::strong_ordering compareString (const S& lhs, const T& rhs); + template <class S, class T> std::weak_ordering compareAsciiNoCase(const S& lhs, const T& rhs); //basic case-insensitive comparison (considering A-Z only!) struct LessAsciiNoCase //STL container predicate { - template <class S> bool operator()(const S& lhs, const S& rhs) const { return compareAsciiNoCase(lhs, rhs) < 0; } + template <class S> bool operator()(const S& lhs, const S& rhs) const { return std::is_lt(compareAsciiNoCase(lhs, rhs)); } }; @@ -186,22 +186,22 @@ Char asciiToLower(Char c) namespace impl { -inline int strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) { return std:: memcmp(ptr1, ptr2, num); } //support embedded 0, unlike strncmp/wcsncmp! -inline int strcmpWithNulls(const wchar_t* ptr1, const wchar_t* ptr2, size_t num) { return std::wmemcmp(ptr1, ptr2, num); } // +//support embedded 0, unlike strncmp/wcsncmp: +inline std::strong_ordering strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) { return std:: memcmp(ptr1, ptr2, num) <=> 0; } +inline std::strong_ordering strcmpWithNulls(const wchar_t* ptr1, const wchar_t* ptr2, size_t num) { return std::wmemcmp(ptr1, ptr2, num) <=> 0; } template <class Char1, class Char2> inline -int strcmpAsciiNoCase(const Char1* lhs, const Char2* rhs, size_t len) +std::weak_ordering strcmpAsciiNoCase(const Char1* lhs, const Char2* rhs, size_t len) { while (len-- > 0) { const Char1 charL = asciiToLower(*lhs++); //ordering: lower-case chars have higher code points than uppper-case const Char2 charR = asciiToLower(*rhs++); // if (charL != charR) - return static_cast<unsigned int>(charL) - static_cast<unsigned int>(charR); //unsigned char-comparison is the convention! - //unsigned underflow is well-defined! + return makeUnsigned(charL) <=> makeUnsigned(charR); //unsigned char-comparison is the convention! } - return 0; + return std::weak_ordering::equivalent; } } @@ -210,7 +210,7 @@ template <class S, class T> inline bool startsWith(const S& str, const T& prefix) { const size_t pfLen = strLength(prefix); - return strLength(str) >= pfLen && impl::strcmpWithNulls(strBegin(str), strBegin(prefix), pfLen) == 0; + return strLength(str) >= pfLen && std::is_eq(impl::strcmpWithNulls(strBegin(str), strBegin(prefix), pfLen)); } @@ -218,7 +218,7 @@ template <class S, class T> inline bool startsWithAsciiNoCase(const S& str, const T& prefix) { const size_t pfLen = strLength(prefix); - return strLength(str) >= pfLen && impl::strcmpAsciiNoCase(strBegin(str), strBegin(prefix), pfLen) == 0; + return strLength(str) >= pfLen && std::is_eq(impl::strcmpAsciiNoCase(strBegin(str), strBegin(prefix), pfLen)); } @@ -227,7 +227,7 @@ bool endsWith(const S& str, const T& postfix) { const size_t strLen = strLength(str); const size_t pfLen = strLength(postfix); - return strLen >= pfLen && impl::strcmpWithNulls(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen) == 0; + return strLen >= pfLen && std::is_eq(impl::strcmpWithNulls(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen)); } @@ -236,7 +236,7 @@ bool endsWithAsciiNoCase(const S& str, const T& postfix) { const size_t strLen = strLength(str); const size_t pfLen = strLength(postfix); - return strLen >= pfLen && impl::strcmpAsciiNoCase(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen) == 0; + return strLen >= pfLen && std::is_eq(impl::strcmpAsciiNoCase(strBegin(str) + strLen - pfLen, strBegin(postfix), pfLen)); } @@ -244,7 +244,7 @@ template <class S, class T> inline bool equalString(const S& lhs, const T& rhs) { const size_t lhsLen = strLength(lhs); - return lhsLen == strLength(rhs) && impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), lhsLen) == 0; + return lhsLen == strLength(rhs) && std::is_eq(impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), lhsLen)); } @@ -252,34 +252,36 @@ template <class S, class T> inline bool equalAsciiNoCase(const S& lhs, const T& rhs) { const size_t lhsLen = strLength(lhs); - return lhsLen == strLength(rhs) && impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), lhsLen) == 0; + return lhsLen == strLength(rhs) && std::is_eq(impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), lhsLen)); } +#if 0 template <class S, class T> inline -int compareString(const S& lhs, const T& rhs) +std::strong_ordering compareString(const S& lhs, const T& rhs) { const size_t lhsLen = strLength(lhs); const size_t rhsLen = strLength(rhs); - //length check *after* strcmpWithNulls(): we do care about natural ordering: e.g. for "compareString(getUpperCase(lhs), getUpperCase(rhs))" - if (const int rv = impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), std::min(lhsLen, rhsLen)); - rv != 0) - return rv; - return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); + //length check *after* strcmpWithNulls(): we DO care about natural ordering: e.g. for "compareString(getUpperCase(lhs), getUpperCase(rhs))" + if (const std::strong_ordering cmp = impl::strcmpWithNulls(strBegin(lhs), strBegin(rhs), std::min(lhsLen, rhsLen)); + std::is_neq(cmp)) + return cmp; + return lhsLen <=> rhsLen; } +#endif template <class S, class T> inline -int compareAsciiNoCase(const S& lhs, const T& rhs) +std::weak_ordering compareAsciiNoCase(const S& lhs, const T& rhs) { const size_t lhsLen = strLength(lhs); const size_t rhsLen = strLength(rhs); - if (const int rv = impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), std::min(lhsLen, rhsLen)); - rv != 0) - return rv; - return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); + if (const std::weak_ordering cmp = impl::strcmpAsciiNoCase(strBegin(lhs), strBegin(rhs), std::min(lhsLen, rhsLen)); + std::is_neq(cmp)) + return cmp; + return lhsLen <=> rhsLen; } @@ -583,17 +585,17 @@ namespace impl { enum class NumberType { - SIGNED_INT, - UNSIGNED_INT, - FLOATING_POINT, - OTHER, + signedInt, + unsignedInt, + floatingPoint, + other, }; -template <class S, class Num> S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::OTHER>) = delete; +template <class S, class Num> S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::other>) = delete; #if 0 //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20) template <class S, class Num> inline -S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::OTHER>) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::other>) { std::basic_ostringstream<GetCharTypeT<S>> ss; ss << number; @@ -603,13 +605,13 @@ S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::OTH template <class S, class Num> inline -S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::FLOATING_POINT>) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::floatingPoint>) { //don't use sprintf("%g"): way SLOWWWWWWER than std::to_chars() char buffer[128]; //zero-initialize? //let's give some leeway, but 24 chars should suffice: https://www.reddit.com/r/cpp/comments/dgj89g/cppcon_2019_stephan_t_lavavej_floatingpoint/f3j7d3q/ - const char* strEnd = to_chars(std::begin(buffer), std::end(buffer), number); + const char* strEnd = toChars(std::begin(buffer), std::end(buffer), number); S output; std::for_each(static_cast<const char*>(buffer), strEnd, @@ -657,7 +659,7 @@ void formatPositiveInteger(Num n, OutputIterator& it) template <class S, class Num> inline -S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::SIGNED_INT>) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::signedInt>) { GetCharTypeT<S> buffer[2 + sizeof(Num) * 241 / 100]; //zero-initialize? //it's generally faster to use a buffer than to rely on String::operator+=() (in)efficiency @@ -677,7 +679,7 @@ S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::SIG template <class S, class Num> inline -S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::UNSIGNED_INT>) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::unsignedInt>) { GetCharTypeT<S> buffer[1 + sizeof(Num) * 241 / 100]; //zero-initialize? //required chars: ceil(ln_10(256^sizeof(n))) =~ ceil(sizeof(n) * 2.4082) <= 1 + floor(sizeof(n) * 2.41) @@ -691,10 +693,10 @@ S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::UNS //-------------------------------------------------------------------------------- -template <class Num, class S> Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::OTHER>) = delete; +template <class Num, class S> Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::other>) = delete; #if 0 //default string to number conversion using streams: convenient, but SLOW template <class Num, class S> inline -Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::OTHER>) +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::other>) { using CharType = GetCharTypeT<S>; Num number = 0; @@ -708,7 +710,7 @@ inline double stringToFloat(const char* first, const char* last) { //don't use std::strtod(): 1. requires null-terminated string 2. SLOWER than std::from_chars() - return from_chars(first, last); + return fromChars(first, last); } @@ -718,12 +720,12 @@ double stringToFloat(const wchar_t* first, const wchar_t* last) std::string buf(last - first, '\0'); std::transform(first, last, buf.begin(), [](wchar_t c) { return static_cast<char>(c); }); - return from_chars(buf.c_str(), buf.c_str() + buf.size()); + return fromChars(buf.c_str(), buf.c_str() + buf.size()); } template <class Num, class S> inline -Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::FLOATING_POINT>) +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::floatingPoint>) { const auto* const first = strBegin(str); const auto* const last = first + strLength(str); @@ -771,7 +773,7 @@ Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to i template <class Num, class S> inline -Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::SIGNED_INT>) +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::signedInt>) { bool hasMinusSign = false; //handle minus sign const Num number = extractInteger<Num>(str, hasMinusSign); @@ -780,7 +782,7 @@ Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::SIGNED template <class Num, class S> inline -Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::UNSIGNED_INT>) //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::unsignedInt>) //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic { bool hasMinusSign = false; //handle minus sign const Num number = extractInteger<Num>(str, hasMinusSign); @@ -798,10 +800,10 @@ template <class S, class Num> inline S numberTo(const Num& number) { using TypeTag = std::integral_constant<impl::NumberType, - IsSignedInt <Num>::value ? impl::NumberType::SIGNED_INT : - IsUnsignedInt<Num>::value ? impl::NumberType::UNSIGNED_INT : - IsFloat <Num>::value ? impl::NumberType::FLOATING_POINT : - impl::NumberType::OTHER>; + IsSignedInt <Num>::value ? impl::NumberType::signedInt : + IsUnsignedInt<Num>::value ? impl::NumberType::unsignedInt : + IsFloat <Num>::value ? impl::NumberType::floatingPoint : + impl::NumberType::other>; return impl::numberTo<S>(number, TypeTag()); } @@ -811,10 +813,10 @@ template <class Num, class S> inline Num stringTo(const S& str) { using TypeTag = std::integral_constant<impl::NumberType, - IsSignedInt <Num>::value ? impl::NumberType::SIGNED_INT : - IsUnsignedInt<Num>::value ? impl::NumberType::UNSIGNED_INT : - IsFloat <Num>::value ? impl::NumberType::FLOATING_POINT : - impl::NumberType::OTHER>; + IsSignedInt <Num>::value ? impl::NumberType::signedInt : + IsUnsignedInt<Num>::value ? impl::NumberType::unsignedInt : + IsFloat <Num>::value ? impl::NumberType::floatingPoint : + impl::NumberType::other>; return impl::stringTo<Num>(str, TypeTag()); } |