From 808aa150f815618b42be9d2cbe73c005215cfaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Sat, 22 Nov 2025 18:41:54 -0800 Subject: [PATCH] StringView constexpr. (#351) --- include/bx/inline/bx.inl | 7 ++ include/bx/inline/string.inl | 198 +++++++++++++++++++++++++++-------- include/bx/platform.h | 4 +- include/bx/readerwriter.h | 8 ++ include/bx/string.h | 102 +++++++++++------- src/dtoa.cpp | 10 +- src/string.cpp | 14 +-- tests/string_test.cpp | 76 +++++++------- 8 files changed, 281 insertions(+), 138 deletions(-) diff --git a/include/bx/inline/bx.inl b/include/bx/inline/bx.inl index 6f06e34..e5f1214 100644 --- a/include/bx/inline/bx.inl +++ b/include/bx/inline/bx.inl @@ -171,6 +171,13 @@ namespace bx return __builtin_bit_cast(Ty, _from); } + template + inline constexpr bool narrowCastTest(Ty* _out, const FromT& _from) + { + *_out = static_cast(_from); + return static_cast(*_out) == _from; + } + template inline Ty narrowCast(const FromT& _from, Location _location) { diff --git a/include/bx/inline/string.inl b/include/bx/inline/string.inl index c526a56..c4a1207 100644 --- a/include/bx/inline/string.inl +++ b/include/bx/inline/string.inl @@ -44,8 +44,8 @@ namespace bx } template - inline constexpr StringLiteral::StringLiteral(const char (&str)[SizeT]) - : m_ptr(str) + inline constexpr StringLiteral::StringLiteral(const char (&_str)[SizeT]) + : m_ptr(_str) , m_len(SizeT - 1) { BX_ASSERT('\0' == m_ptr[SizeT - 1], "Must be 0 terminated."); @@ -61,20 +61,21 @@ namespace bx return m_ptr; } - inline void StringLiteral::clear() + inline constexpr void StringLiteral::clear() { m_ptr = ""; m_len = 0; } - inline bool StringLiteral::isEmpty() const + inline constexpr bool StringLiteral::isEmpty() const { return 0 == m_len; } - inline StringView::StringView() + inline constexpr StringView::StringView() { - clear(); + m_ptr = ""; + m_len = 0; } inline constexpr StringView::StringView(const StringLiteral& _str) @@ -84,49 +85,49 @@ namespace bx { } - inline StringView::StringView(const StringView& _rhs) + inline constexpr StringView::StringView(const StringView& _rhs) { set(_rhs); } - inline StringView::StringView(const StringView& _rhs, int32_t _start, int32_t _len) + inline constexpr StringView::StringView(const StringView& _rhs, int32_t _start, int32_t _len) { set(_rhs, _start, _len); } - inline StringView& StringView::operator=(const char* _rhs) + inline constexpr StringView& StringView::operator=(const char* _rhs) { set(_rhs); return *this; } - inline StringView& StringView::operator=(const StringView& _rhs) + inline constexpr StringView& StringView::operator=(const StringView& _rhs) { set(_rhs); return *this; } - inline StringView::StringView(const char* _ptr) + inline constexpr StringView::StringView(const char* _ptr) { set(_ptr, INT32_MAX); } - inline StringView::StringView(const char* _ptr, int32_t _len) + inline constexpr StringView::StringView(const char* _ptr, int32_t _len) { set(_ptr, _len); } - inline StringView::StringView(const char* _ptr, const char* _term) + inline constexpr StringView::StringView(const char* _ptr, const char* _term) { set(_ptr, _term); } - inline void StringView::set(const char* _ptr) + inline constexpr void StringView::set(const char* _ptr) { set(_ptr, INT32_MAX); } - inline void StringView::set(const char* _ptr, int32_t _len) + inline constexpr void StringView::set(const char* _ptr, int32_t _len) { clear(); @@ -138,68 +139,99 @@ namespace bx } } - inline void StringView::set(const char* _ptr, const char* _term) + inline constexpr void StringView::set(const char* _ptr, const char* _term) { set(_ptr, int32_t(_term-_ptr) ); } - inline void StringView::set(const StringView& _str) + inline constexpr void StringView::set(const StringView& _str) { set(_str, 0, INT32_MAX); } - inline void StringView::set(const StringView& _str, int32_t _start, int32_t _len) + inline constexpr void StringView::set(const StringView& _str, int32_t _start, int32_t _len) { const int32_t start = min(_start, _str.m_len); const int32_t len = clamp(_str.m_len - start, 0, min(_len, _str.m_len) ); set(_str.m_ptr + start, len); } - inline void StringView::clear() + inline constexpr void StringView::clear() { m_ptr = ""; m_len = 0; m_0terminated = true; } - inline const char* StringView::getPtr() const + inline constexpr const char* StringView::getPtr() const { return m_ptr; } - inline const char* StringView::getTerm() const + inline constexpr const char* StringView::getTerm() const { return m_ptr + m_len; } - inline bool StringView::isEmpty() const + inline constexpr bool StringView::isEmpty() const { return 0 == m_len; } - inline int32_t StringView::getLength() const + inline constexpr int32_t StringView::getLength() const { return m_len; } - inline bool StringView::is0Terminated() const + inline constexpr bool StringView::is0Terminated() const { return m_0terminated; } - inline bool operator==(const StringView& _lhs, const StringView& _rhs) + inline constexpr bool operator==(const StringView& _lhs, const StringView& _rhs) { - return 0 == strCmp(_lhs, _rhs); + const int32_t len = _lhs.getLength(); + + if (len != _rhs.getLength() ) + { + return false; + } + + if (0 == len) + { + return true; + } + + const char* lhs = _lhs.getPtr(); + const char* rhs = _rhs.getPtr(); + + if constexpr (!isConstantEvaluated() ) + { + // note: comparison of addresses of literals has unspecified value + if (lhs == rhs) + { + return true; + } + } + + for (int32_t ii = 0, num = len-1 + ; ii < num && *lhs == *rhs + ; ++ii, ++lhs, ++rhs + ) + { + } + + return *lhs == *rhs; } - inline bool overlap(const StringView& _a, const StringView& _b) + inline constexpr bool overlap(const StringView& _a, const StringView& _b) { return _a.getTerm() > _b.getPtr() && _b.getTerm() > _a.getPtr() ; } - inline bool contain(const StringView& _a, const StringView& _b) + inline constexpr bool contain(const StringView& _a, const StringView& _b) { return _a.getPtr() <= _b.getPtr() && _a.getTerm() >= _b.getTerm() @@ -207,76 +239,76 @@ namespace bx } template - inline FixedStringT::FixedStringT() + inline constexpr FixedStringT::FixedStringT() : m_len(0) { } template - inline FixedStringT::FixedStringT(const char* _str) + inline constexpr FixedStringT::FixedStringT(const char* _str) : FixedStringT() { set(_str); } template - inline FixedStringT::FixedStringT(const StringView& _str) + inline constexpr FixedStringT::FixedStringT(const StringView& _str) : FixedStringT() { set(_str); } template - inline FixedStringT::~FixedStringT() + inline constexpr FixedStringT::~FixedStringT() { } template - inline void FixedStringT::set(const char* _str) + inline constexpr void FixedStringT::set(const char* _str) { set(StringView(_str) ); } template - inline void FixedStringT::set(const StringView& _str) + inline constexpr void FixedStringT::set(const StringView& _str) { int32_t copied = strCopy(m_storage, MaxCapacityT, _str); m_len = copied; } template - inline void FixedStringT::append(const StringView& _str) + inline constexpr void FixedStringT::append(const StringView& _str) { m_len += strCopy(&m_storage[m_len], MaxCapacityT-m_len, _str); } template - inline void FixedStringT::clear() + inline constexpr void FixedStringT::clear() { m_len = 0; m_storage[0] = '\0'; } template - inline bool FixedStringT::isEmpty() const + inline constexpr bool FixedStringT::isEmpty() const { return 0 == m_len; } template - inline int32_t FixedStringT::getLength() const + inline constexpr int32_t FixedStringT::getLength() const { return m_len; } template - inline const char* FixedStringT::getCPtr() const + inline constexpr const char* FixedStringT::getCPtr() const { return m_storage; } template - inline FixedStringT::operator StringView() const + inline constexpr FixedStringT::operator StringView() const { return StringView(m_storage, m_len); } @@ -438,11 +470,23 @@ namespace bx return m_line; } - inline int32_t strLen(const StringView& _str, int32_t _max) + inline constexpr int32_t strLen(const StringView& _str, int32_t _max) { return min(_str.getLength(), _max); } + inline constexpr int32_t strLen(const char* _str, int32_t _max) + { + if (NULL == _str) + { + return 0; + } + + const char* ptr = _str; + for (; 0 < _max && *ptr != '\0'; ++ptr, --_max) {}; + return int32_t(ptr - _str); + } + inline bool hasPrefix(const StringView& _str, const StringView& _prefix) { const int32_t len = _prefix.getLength(); @@ -479,4 +523,76 @@ namespace bx return _str; } + inline bool fromString(int8_t* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + + inline bool fromString(uint8_t* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + + inline bool fromString(int16_t* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + + inline bool fromString(uint16_t* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + + inline bool fromString(int32_t* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + + inline bool fromString(uint32_t* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + + inline bool fromString(long* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + + inline bool fromString(unsigned long* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + + inline bool fromString(unsigned long long* _out, const StringView& _str) + { + long long tmp; + fromString(&tmp, _str); + + return narrowCastTest(_out, tmp); + } + } // namespace bx diff --git a/include/bx/platform.h b/include/bx/platform.h index b43e02e..09dd523 100644 --- a/include/bx/platform.h +++ b/include/bx/platform.h @@ -452,9 +452,9 @@ #if defined(__cplusplus) -static_assert(__cplusplus >= BX_LANGUAGE_CPP17, "\n\n" +static_assert(__cplusplus >= BX_LANGUAGE_CPP20, "\n\n" "\t** IMPORTANT! **\n\n" - "\tC++17 standard support is required to build.\n" + "\tC++20 standard support is required to build.\n" "\t\n"); // https://releases.llvm.org/ diff --git a/include/bx/readerwriter.h b/include/bx/readerwriter.h index 9848cb3..b78d81c 100644 --- a/include/bx/readerwriter.h +++ b/include/bx/readerwriter.h @@ -282,6 +282,14 @@ namespace bx /// Write C string. int32_t write(WriterI* _writer, const char* _str, Error* _err); + /// + template + inline int32_t write(WriterI* _writer, const Ty& _value, Error* _err); + + /// + template<> + int32_t write(WriterI* _writer, const StringView& _str, Error* _err); + /// Write formatted string. int32_t write(WriterI* _writer, const StringView& _format, va_list _argList, Error* _err); diff --git a/include/bx/string.h b/include/bx/string.h index 9452806..e3f40b8 100644 --- a/include/bx/string.h +++ b/include/bx/string.h @@ -32,7 +32,7 @@ namespace bx /// Construct string literal from C-style string literal. /// template - constexpr StringLiteral(const char (&str)[SizeT]); + constexpr StringLiteral(const char (&_str)[SizeT]); /// Returns string length. /// @@ -43,11 +43,11 @@ namespace bx constexpr const char* getCPtr() const; /// - void clear(); + constexpr void clear(); /// Returns `true` if string is empty. /// - bool isEmpty() const; + constexpr bool isEmpty() const; private: const char* m_ptr; @@ -61,75 +61,75 @@ namespace bx public: /// Construct default/empty string view. /// - StringView(); + constexpr StringView(); /// Construct string view from string literal. /// constexpr StringView(const StringLiteral& _str); /// - StringView(const StringView& _rhs); + constexpr StringView(const StringView& _rhs); /// - StringView(const StringView& _rhs, int32_t _start, int32_t _len); + constexpr StringView(const StringView& _rhs, int32_t _start, int32_t _len); /// - StringView& operator=(const char* _rhs); + constexpr StringView& operator=(const char* _rhs); /// - StringView& operator=(const StringView& _rhs); + constexpr StringView& operator=(const StringView& _rhs); /// - StringView(const char* _ptr); + constexpr StringView(const char* _ptr); /// - StringView(const char* _ptr, int32_t _len); + constexpr StringView(const char* _ptr, int32_t _len); /// - StringView(const char* _ptr, const char* _term); + constexpr StringView(const char* _ptr, const char* _term); /// - void set(const char* _ptr); + constexpr void set(const char* _ptr); /// - void set(const char* _ptr, int32_t _len); + constexpr void set(const char* _ptr, int32_t _len); /// - void set(const char* _ptr, const char* _term); + constexpr void set(const char* _ptr, const char* _term); /// - void set(const StringView& _str); + constexpr void set(const StringView& _str); /// - void set(const StringView& _str, int32_t _start, int32_t _len); + constexpr void set(const StringView& _str, int32_t _start, int32_t _len); /// - void clear(); + constexpr void clear(); /// Returns pointer to non-terminated string. /// /// @attention Use of this pointer in standard C/C++ functions is not safe. You must use it /// in conjunction with `getTerm()` or getLength()`. /// - const char* getPtr() const; + constexpr const char* getPtr() const; /// Returns pointer past last character in string view. /// /// @attention Dereferencing this pointer is not safe. /// - const char* getTerm() const; + constexpr const char* getTerm() const; /// Returns `true` if string is empty. /// - bool isEmpty() const; + constexpr bool isEmpty() const; /// Returns string length. /// - int32_t getLength() const; + constexpr int32_t getLength() const; /// Returns `true` if string is zero terminated. /// - bool is0Terminated() const; + constexpr bool is0Terminated() const; protected: const char* m_ptr; @@ -138,13 +138,13 @@ namespace bx }; /// Compare two string views. - bool operator==(const StringView& _lhs, const StringView& _rhs); + constexpr bool operator==(const StringView& _lhs, const StringView& _rhs); /// Returns true if two string views overlap. - bool overlap(const StringView& _a, const StringView& _b); + constexpr bool overlap(const StringView& _a, const StringView& _b); /// Returns true if string view `_a` contains string view `_b`. - bool contain(const StringView& _a, const StringView& _b); + constexpr bool contain(const StringView& _a, const StringView& _b); /// Fixed capacity string. /// @@ -153,44 +153,44 @@ namespace bx { public: /// - FixedStringT(); + constexpr FixedStringT(); /// - FixedStringT(const char* _str); + constexpr FixedStringT(const char* _str); /// - FixedStringT(const StringView& _str); + constexpr FixedStringT(const StringView& _str); /// - ~FixedStringT(); + constexpr ~FixedStringT(); /// - void set(const char* _str); + constexpr void set(const char* _str); /// - void set(const StringView& _str); + constexpr void set(const StringView& _str); /// - void append(const StringView& _str); + constexpr void append(const StringView& _str); /// - void clear(); + constexpr void clear(); /// Returns `true` if string is empty. /// - bool isEmpty() const; + constexpr bool isEmpty() const; /// Returns string length. /// - int32_t getLength() const; + constexpr int32_t getLength() const; /// Returns zero-terminated C string pointer. /// - const char* getCPtr() const; + constexpr const char* getCPtr() const; /// Implicitly converts FixedStringT to StringView. /// - operator StringView() const; + constexpr operator StringView() const; private: char m_storage[MaxCapacityT]; @@ -342,10 +342,10 @@ namespace bx int32_t strCmpV(const StringView& _lhs, const StringView& _rhs, int32_t _max = INT32_MAX); /// Get string length. - int32_t strLen(const char* _str, int32_t _max = INT32_MAX); + constexpr int32_t strLen(const char* _str, int32_t _max = INT32_MAX); /// Get string length. - int32_t strLen(const StringView& _str, int32_t _max = INT32_MAX); + constexpr int32_t strLen(const StringView& _str, int32_t _max = INT32_MAX); /// Copy _num characters from string _src to _dst buffer of maximum _dstSize capacity /// including zero terminator. Copy will be terminated with '\0'. @@ -480,12 +480,36 @@ namespace bx /// Converts string to double value. bool fromString(double* _out, const StringView& _str); + /// Converts string to 8-bit integer value. + bool fromString(int8_t* _out, const StringView& _str); + + /// Converts string to 8-bit unsigned integer value. + bool fromString(uint8_t* _out, const StringView& _str); + + /// Converts string to 8-bit integer value. + bool fromString(int16_t* _out, const StringView& _str); + + /// Converts string to 8-bit unsigned integer value. + bool fromString(uint16_t* _out, const StringView& _str); + /// Converts string to 32-bit integer value. bool fromString(int32_t* _out, const StringView& _str); /// Converts string to 32-bit unsigned integer value. bool fromString(uint32_t* _out, const StringView& _str); + /// Converts string to + bool fromString(long* _out, const StringView& _str); + + /// Converts string to + bool fromString(unsigned long* _out, const StringView& _str); + + /// Converts string to 64-bit long long value. + bool fromString(long long* _out, const StringView& _str); + + /// Converts string to 64-bit unsigned long long value. + bool fromString(unsigned long long* _out, const StringView& _str); + /// class LineReader { diff --git a/src/dtoa.cpp b/src/dtoa.cpp index b148d34..efff72c 100644 --- a/src/dtoa.cpp +++ b/src/dtoa.cpp @@ -1093,7 +1093,7 @@ namespace bx return true; } - bool fromString(int32_t* _out, const StringView& _str) + bool fromString(long long* _out, const StringView& _str) { StringView str = strLTrimSpace(_str); @@ -1113,7 +1113,7 @@ namespace bx break; } - int32_t result = 0; + long long result = 0; for (ch = *ptr++; isNumeric(ch) && ptr <= term; ch = *ptr++) { @@ -1125,10 +1125,4 @@ namespace bx return true; } - bool fromString(uint32_t* _out, const StringView& _str) - { - fromString( (int32_t*)_out, _str); - return true; - } - } // namespace bx diff --git a/src/string.cpp b/src/string.cpp index 1bb2fa1..0a83dac 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -165,7 +165,7 @@ namespace bx typedef char (*CharFn)(char _ch); - inline char toNoop(char _ch) + inline constexpr char toNoop(char _ch) { return _ch; } @@ -290,18 +290,6 @@ namespace bx ); } - int32_t strLen(const char* _str, int32_t _max) - { - if (NULL == _str) - { - return 0; - } - - const char* ptr = _str; - for (; 0 < _max && *ptr != '\0'; ++ptr, --_max) {}; - return int32_t(ptr - _str); - } - inline int32_t strCopy(char* _dst, int32_t _dstSize, const char* _src, int32_t _num) { BX_ASSERT(NULL != _dst, "_dst can't be NULL!"); diff --git a/tests/string_test.cpp b/tests/string_test.cpp index 434b379..639a20a 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -13,41 +13,6 @@ bx::AllocatorI* g_allocator; -TEST_CASE("StringLiteral", "[string]") -{ - constexpr bx::StringLiteral tmp[] = { "1389", "abvgd", "mac", "pod" }; - - REQUIRE(bx::isSorted(tmp, BX_COUNTOF(tmp) ) ); - - STATIC_REQUIRE(4 == tmp[0].getLength() ); - REQUIRE(4 == bx::strLen(tmp[0]) ); - REQUIRE(0 == bx::strCmp("1389", tmp[0]) ); - - STATIC_REQUIRE(5 == tmp[1].getLength() ); - REQUIRE(5 == bx::strLen(tmp[1]) ); - REQUIRE(0 == bx::strCmp("abvgd", tmp[1]) ); - - STATIC_REQUIRE(3 == tmp[2].getLength() ); - REQUIRE(3 == bx::strLen(tmp[2]) ); - REQUIRE(0 == bx::strCmp("mac", tmp[2]) ); - - STATIC_REQUIRE(3 == tmp[3].getLength() ); - REQUIRE(3 == bx::strLen(tmp[3]) ); - REQUIRE(0 == bx::strCmp("pod", tmp[3]) ); - - constexpr bx::StringLiteral copy(tmp[0]); - - STATIC_REQUIRE(4 == copy.getLength() ); - REQUIRE(4 == bx::strLen(copy) ); - REQUIRE(0 == bx::strCmp("1389", copy) ); - - constexpr bx::StringView sv(tmp[1]); - - REQUIRE(5 == sv.getLength() ); - REQUIRE(5 == bx::strLen(sv) ); - REQUIRE("abvgd" == sv); -} - TEST_CASE("stringPrintfTy", "[string]") { std::string test; @@ -492,6 +457,47 @@ TEST_CASE("fromString int32_t", "[string]") REQUIRE(testFromString(-21, "-021") ); } +TEST_CASE("StringLiteral", "[string]") +{ + constexpr bx::StringLiteral tmp[] = { "1389", "abvgd", "mac", "pod" }; + + REQUIRE(bx::isSorted(tmp, BX_COUNTOF(tmp) ) ); + + STATIC_REQUIRE(4 == tmp[0].getLength() ); + STATIC_REQUIRE(4 == bx::strLen(tmp[0]) ); + REQUIRE(0 == bx::strCmp("1389", tmp[0]) ); + + STATIC_REQUIRE(5 == tmp[1].getLength() ); + STATIC_REQUIRE(5 == bx::strLen(tmp[1]) ); + REQUIRE(0 == bx::strCmp("abvgd", tmp[1]) ); + + STATIC_REQUIRE(3 == tmp[2].getLength() ); + STATIC_REQUIRE(3 == bx::strLen(tmp[2]) ); + REQUIRE(0 == bx::strCmp("mac", tmp[2]) ); + + STATIC_REQUIRE(3 == tmp[3].getLength() ); + STATIC_REQUIRE(3 == bx::strLen(tmp[3]) ); + REQUIRE(0 == bx::strCmp("pod", tmp[3]) ); + + constexpr bx::StringLiteral copy(tmp[0]); + STATIC_REQUIRE(4 == copy.getLength() ); + REQUIRE(4 == bx::strLen(copy) ); + REQUIRE(0 == bx::strCmp("1389", copy) ); + + constexpr bx::StringView sv(tmp[1]); + STATIC_REQUIRE(5 == sv.getLength() ); + STATIC_REQUIRE(5 == bx::strLen(sv) ); + STATIC_REQUIRE("abvgd" == sv); +} + +TEST_CASE("StringView constexpr", "[string]") +{ + constexpr bx::StringView sv("1389"); + + STATIC_REQUIRE(sv == "1389"); + STATIC_REQUIRE(4 == bx::strLen(sv) ); +} + TEST_CASE("StringView", "[string]") { bx::StringView sv("test");