From de6ae0b6ef2b4129fea2ba2e60ab0223c912423c Mon Sep 17 00:00:00 2001
From: osidorkin <osidorkin@yandex-team.com>
Date: Sat, 25 Mar 2023 23:48:17 +0300
Subject: More precise buffer allocation based on base and type, more tests

---
 util/string/cast.h | 43 +++++++++++++++++++++++++++++++------------
 1 file changed, 31 insertions(+), 12 deletions(-)

(limited to 'util/string/cast.h')

diff --git a/util/string/cast.h b/util/string/cast.h
index b2ebce9a88..581a0c329b 100644
--- a/util/string/cast.h
+++ b/util/string/cast.h
@@ -387,18 +387,34 @@ static inline TUtf32String ToUtf32String(const TUtf32StringBuf wtr) {
     return TUtf32String(wtr);
 }
 
-template <typename T, unsigned base = 10, class TChar = char>
+template <typename T, unsigned radix = 10, class TChar = char>
 class TIntStringBuf {
 private:
     // inline constexprs are not supported by CUDA yet
     static constexpr char IntToChar[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+    static_assert(1 < radix && radix < 17, "expect 1 < radix && radix < 17");
+
+    // auxiliary recursive template used to calculate maximum buffer size for the given type
+    template <T v>
+    struct TBufSizeRec {
+        // MSVC is tries to evaluate both sides of ?: operator and doesn't break recursion
+        static constexpr ui32 GetValue() {
+            if (v == 0) {
+                return 1;
+            }
+            return 1 + TBufSizeRec<v / radix>::value;
+        }
 
-    static_assert(1 < base && base < 17, "expect 1 < base && base < 17");
+        static constexpr ui32 value = GetValue();
+    };
 
 public:
+    static constexpr ui32 bufferSize = (std::is_signed<T>::value ? 1 : 0) +
+                                       ((radix == 2) ? sizeof(T) * 8 : TBufSizeRec<std::numeric_limits<T>::max()>::value);
+
     template <std::enable_if_t<std::is_integral<T>::value, bool> = true>
     explicit constexpr TIntStringBuf(T t) {
-        Size_ = Convert(t, Buf_, sizeof(Buf_) - 1);
+        Size_ = Convert(t, Buf_, sizeof(Buf_));
         // Init the rest of the array,
         // otherwise constexpr copy and move constructors don't work due to uninitialized data access
         std::fill(Buf_ + Size_, Buf_ + sizeof(Buf_), '\0');
@@ -408,7 +424,8 @@ public:
         return TStringBuf(Buf_, Size_);
     }
 
-    constexpr static ui32 Convert(T t, TChar* buf, ui32 bufLen) {
+    constexpr static ui32 Convert(T t, TChar* buf, size_t bufLen) {
+        bufLen = std::min<size_t>(bufferSize, bufLen);
         if (std::is_signed<T>::value && t < 0) {
             Y_ENSURE(bufLen >= 2, TStringBuf("not enough room in buffer"));
             buf[0] = '-';
@@ -430,10 +447,10 @@ private:
         auto* be = buf + bufLen;
         ui32 l = 0;
         while (t > 0 && be > buf) {
-            const auto v = t / base;
-            const auto r = (base == 2 || base == 4 || base == 8 || base == 16) ? t & (base - 1) : t - base * v;
+            const auto v = t / radix;
+            const auto r = (radix == 2 || radix == 4 || radix == 8 || radix == 16) ? t & (radix - 1) : t - radix * v;
             --be;
-            if /*constexpr*/ (base <= 10) { // if constexpr is not supported by CUDA yet
+            if /*constexpr*/ (radix <= 10) { // if constexpr is not supported by CUDA yet
                 *be = r + '0';
             } else {
                 *be = IntToChar[r];
@@ -442,13 +459,15 @@ private:
             t = v;
         }
         Y_ENSURE(!t, TStringBuf("not enough room in buffer"));
-        for (ui32 i = 0; i < l; ++i) {
-            *buf = *be;
-            ++buf;
-            ++be;
+        if (buf != be) {
+            for (ui32 i = 0; i < l; ++i) {
+                *buf = *be;
+                ++buf;
+                ++be;
+            }
         }
         return l;
     }
     ui32 Size_;
-    TChar Buf_[sizeof(T) * 8]; // worst case base = 2
+    TChar Buf_[bufferSize];
 };
-- 
cgit v1.2.3