From ed3a02bd5bbfbca337dbc0403fa4d04b955d05ec Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 20 Apr 2026 21:40:02 +0200 Subject: Add CompactString utility type (#990) - Introduce `CompactString`: a move-only, heap-allocated, immutable string wrapper that stores its length in a prefix byte for cheap `Size()`/`ToView()` while keeping the object to a single pointer. - Swap the `ToString()` integer-formatting helpers in `zencore/string.cpp` to `std::to_chars`, which is ~5-10x faster and benefits every `IntNum` / `StringBuilder` / `CbJsonWriter` caller. - No in-tree users on `main` yet; the type is ready for callers that want owned-string storage with lower per-entry overhead than `std::string` (e.g. long-lived log buffers, session records). --- src/zencore/string.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 4 deletions(-) (limited to 'src/zencore/string.cpp') diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp index 44f78aa75..2691d14b8 100644 --- a/src/zencore/string.cpp +++ b/src/zencore/string.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -54,15 +55,21 @@ namespace zen { bool ToString(std::span Buffer, uint64_t Num) { - snprintf(Buffer.data(), Buffer.size(), "%" PRIu64, Num); - + auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num); + if (Ec == std::errc{}) + { + *Ptr = '\0'; + } return true; } bool ToString(std::span Buffer, int64_t Num) { - snprintf(Buffer.data(), Buffer.size(), "%" PRId64, Num); - + auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num); + if (Ec == std::errc{}) + { + *Ptr = '\0'; + } return true; } @@ -1341,6 +1348,81 @@ TEST_CASE("hidesensitivestring") CHECK_EQ(HideSensitiveString("1234567890123456789"sv), "1234XXXX..."sv); } +TEST_CASE("CompactString.default") +{ + CompactString S; + CHECK(S.IsEmpty()); + CHECK(S.Size() == 0); + CHECK(S.ToView() == std::string_view()); + CHECK(S.c_str()[0] == '\0'); +} + +TEST_CASE("CompactString.empty") +{ + CompactString S(std::string_view("")); + CHECK(S.IsEmpty()); + CHECK(S.Size() == 0); +} + +TEST_CASE("CompactString.short") +{ + CompactString S(std::string_view("hello")); + CHECK(!S.IsEmpty()); + CHECK(S.Size() == 5); + CHECK(S.ToView() == std::string_view("hello")); +} + +TEST_CASE("CompactString.sentinel_boundary") +{ + // 254 chars — largest value that fits in the prefix byte + std::string Str254(254, 'x'); + std::string_view View254(Str254); + CompactString S(View254); + CHECK(S.Size() == 254); + CHECK(S.ToView() == View254); +} + +TEST_CASE("CompactString.sentinel_exact") +{ + // 255 chars — hits the 0xFF sentinel, falls back to strlen + std::string Str255(255, 'y'); + std::string_view View255(Str255); + CompactString S(View255); + CHECK(S.Size() == 255); + CHECK(S.ToView() == View255); +} + +TEST_CASE("CompactString.long") +{ + // Well beyond the sentinel + std::string Str512(512, 'z'); + std::string_view View512(Str512); + CompactString S(View512); + CHECK(S.Size() == 512); + CHECK(S.ToView() == View512); +} + +TEST_CASE("CompactString.move") +{ + CompactString A(std::string_view("test")); + CompactString B(std::move(A)); + CHECK(A.IsEmpty()); + CHECK(B.ToView() == std::string_view("test")); + + CompactString C(std::string_view("first")); + CompactString D(std::string_view("second")); + D = std::move(C); + CHECK(C.IsEmpty()); + CHECK(D.ToView() == std::string_view("first")); +} + +TEST_CASE("CompactString.implicit_conversion") +{ + CompactString S(std::string_view("view")); + std::string_view V = S; + CHECK(V == std::string_view("view")); +} + TEST_SUITE_END(); void -- cgit v1.2.3