duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

charconv.hpp (92598B)


      1 #ifndef _C4_CHARCONV_HPP_
      2 #define _C4_CHARCONV_HPP_
      3 
      4 /** @file charconv.hpp Lightweight generic type-safe wrappers for
      5  * converting individual values to/from strings.
      6  *
      7  * These are the main functions:
      8  *
      9  * @code{.cpp}
     10  * // Convert the given value, writing into the string.
     11  * // The resulting string will NOT be null-terminated.
     12  * // Return the number of characters needed.
     13  * // This function is safe to call when the string is too small -
     14  * // no writes will occur beyond the string's last character.
     15  * template<class T> size_t c4::to_chars(substr buf, T const& C4_RESTRICT val);
     16  *
     17  *
     18  * // Convert the given value to a string using to_chars(), and
     19  * // return the resulting string, up to and including the last
     20  * // written character.
     21  * template<class T> substr c4::to_chars_sub(substr buf, T const& C4_RESTRICT val);
     22  *
     23  *
     24  * // Read a value from the string, which must be
     25  * // trimmed to the value (ie, no leading/trailing whitespace).
     26  * // return true if the conversion succeeded.
     27  * // There is no check for overflow; the value wraps around in a way similar
     28  * // to the standard C/C++ overflow behavior. For example,
     29  * // from_chars<int8_t>("128", &val) returns true and val will be
     30  * // set tot 0.
     31  * template<class T> bool c4::from_chars(csubstr buf, T * C4_RESTRICT val);
     32  *
     33  *
     34  * // Read the first valid sequence of characters from the string,
     35  * // skipping leading whitespace, and convert it using from_chars().
     36  * // Return the number of characters read for converting.
     37  * template<class T> size_t c4::from_chars_first(csubstr buf, T * C4_RESTRICT val);
     38  * @endcode
     39  */
     40 
     41 #include "c4/language.hpp"
     42 #include <inttypes.h>
     43 #include <type_traits>
     44 #include <climits>
     45 #include <limits>
     46 #include <utility>
     47 
     48 #include "c4/config.hpp"
     49 #include "c4/substr.hpp"
     50 #include "c4/std/std_fwd.hpp"
     51 #include "c4/memory_util.hpp"
     52 #include "c4/szconv.hpp"
     53 
     54 #ifndef C4CORE_NO_FAST_FLOAT
     55 #   if (C4_CPP >= 17)
     56 #       if defined(_MSC_VER)
     57 #           if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros
     58 #               include <charconv>
     59 #               define C4CORE_HAVE_STD_TOCHARS 1
     60 #               define C4CORE_HAVE_STD_FROMCHARS 0 // prefer fast_float with MSVC
     61 #               define C4CORE_HAVE_FAST_FLOAT 1
     62 #           else
     63 #               define C4CORE_HAVE_STD_TOCHARS 0
     64 #               define C4CORE_HAVE_STD_FROMCHARS 0
     65 #               define C4CORE_HAVE_FAST_FLOAT 1
     66 #           endif
     67 #       else
     68 #           if __has_include(<charconv>)
     69 #               include <charconv>
     70 #               if defined(__cpp_lib_to_chars)
     71 #                   define C4CORE_HAVE_STD_TOCHARS 1
     72 #                   define C4CORE_HAVE_STD_FROMCHARS 0 // glibc uses fast_float internally
     73 #                   define C4CORE_HAVE_FAST_FLOAT 1
     74 #               else
     75 #                   define C4CORE_HAVE_STD_TOCHARS 0
     76 #                   define C4CORE_HAVE_STD_FROMCHARS 0
     77 #                   define C4CORE_HAVE_FAST_FLOAT 1
     78 #               endif
     79 #           else
     80 #               define C4CORE_HAVE_STD_TOCHARS 0
     81 #               define C4CORE_HAVE_STD_FROMCHARS 0
     82 #               define C4CORE_HAVE_FAST_FLOAT 1
     83 #           endif
     84 #       endif
     85 #   else
     86 #       define C4CORE_HAVE_STD_TOCHARS 0
     87 #       define C4CORE_HAVE_STD_FROMCHARS 0
     88 #       define C4CORE_HAVE_FAST_FLOAT 1
     89 #   endif
     90 #   if C4CORE_HAVE_FAST_FLOAT
     91         C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wsign-conversion")
     92         C4_SUPPRESS_WARNING_GCC("-Warray-bounds")
     93 #       if defined(__GNUC__) && __GNUC__ >= 5
     94             C4_SUPPRESS_WARNING_GCC("-Wshift-count-overflow")
     95 #       endif
     96 //#       include "c4/ext/fast_float.hpp"
     97 #include "fast_float/fast_float.h"
     98         C4_SUPPRESS_WARNING_GCC_POP
     99 #   endif
    100 #elif (C4_CPP >= 17)
    101 #   define C4CORE_HAVE_FAST_FLOAT 0
    102 #   if defined(_MSC_VER)
    103 #       if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros
    104 #           include <charconv>
    105 #           define C4CORE_HAVE_STD_TOCHARS 1
    106 #           define C4CORE_HAVE_STD_FROMCHARS 1
    107 #       else
    108 #           define C4CORE_HAVE_STD_TOCHARS 0
    109 #           define C4CORE_HAVE_STD_FROMCHARS 0
    110 #       endif
    111 #   else
    112 #       if __has_include(<charconv>)
    113 #           include <charconv>
    114 #           if defined(__cpp_lib_to_chars)
    115 #               define C4CORE_HAVE_STD_TOCHARS 1
    116 #               define C4CORE_HAVE_STD_FROMCHARS 1 // glibc uses fast_float internally
    117 #           else
    118 #               define C4CORE_HAVE_STD_TOCHARS 0
    119 #               define C4CORE_HAVE_STD_FROMCHARS 0
    120 #           endif
    121 #       else
    122 #           define C4CORE_HAVE_STD_TOCHARS 0
    123 #           define C4CORE_HAVE_STD_FROMCHARS 0
    124 #       endif
    125 #   endif
    126 #else
    127 #   define C4CORE_HAVE_STD_TOCHARS 0
    128 #   define C4CORE_HAVE_STD_FROMCHARS 0
    129 #   define C4CORE_HAVE_FAST_FLOAT 0
    130 #endif
    131 
    132 
    133 #if !C4CORE_HAVE_STD_FROMCHARS
    134 #include <cstdio>
    135 #endif
    136 
    137 
    138 #if defined(_MSC_VER)
    139 #   pragma warning(push)
    140 #   pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe
    141 #   if C4_MSVC_VERSION != C4_MSVC_VERSION_2017
    142 #       pragma warning(disable: 4800) //'int': forcing value to bool 'true' or 'false' (performance warning)
    143 #   endif
    144 #endif
    145 
    146 #if defined(__clang__)
    147 #   pragma clang diagnostic push
    148 #   pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
    149 #   pragma clang diagnostic ignored "-Wformat-nonliteral"
    150 #   pragma clang diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision
    151 #   pragma clang diagnostic ignored "-Wold-style-cast"
    152 #elif defined(__GNUC__)
    153 #   pragma GCC diagnostic push
    154 #   pragma GCC diagnostic ignored "-Wformat-nonliteral"
    155 #   pragma GCC diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision
    156 #   pragma GCC diagnostic ignored "-Wuseless-cast"
    157 #   pragma GCC diagnostic ignored "-Wold-style-cast"
    158 #endif
    159 
    160 
    161 namespace c4 {
    162 
    163 #if C4CORE_HAVE_STD_TOCHARS
    164 /** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */
    165 typedef enum : std::underlying_type<std::chars_format>::type {
    166     /** print the real number in floating point format (like %f) */
    167     FTOA_FLOAT = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::fixed),
    168     /** print the real number in scientific format (like %e) */
    169     FTOA_SCIENT = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::scientific),
    170     /** print the real number in flexible format (like %g) */
    171     FTOA_FLEX = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::general),
    172     /** print the real number in hexadecimal format (like %a) */
    173     FTOA_HEXA = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::hex),
    174 } RealFormat_e;
    175 #else
    176 /** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */
    177 typedef enum : char {
    178     /** print the real number in floating point format (like %f) */
    179     FTOA_FLOAT = 'f',
    180     /** print the real number in scientific format (like %e) */
    181     FTOA_SCIENT = 'e',
    182     /** print the real number in flexible format (like %g) */
    183     FTOA_FLEX = 'g',
    184     /** print the real number in hexadecimal format (like %a) */
    185     FTOA_HEXA = 'a',
    186 } RealFormat_e;
    187 #endif
    188 
    189 
    190 /** in some platforms, int,unsigned int
    191  *  are not any of int8_t...int64_t and
    192  *  long,unsigned long are not any of uint8_t...uint64_t */
    193 template<class T>
    194 struct is_fixed_length
    195 {
    196     enum : bool {
    197         /** true if T is one of the fixed length signed types */
    198         value_i = (std::is_integral<T>::value
    199                    && (std::is_same<T, int8_t>::value
    200                        || std::is_same<T, int16_t>::value
    201                        || std::is_same<T, int32_t>::value
    202                        || std::is_same<T, int64_t>::value)),
    203         /** true if T is one of the fixed length unsigned types */
    204         value_u = (std::is_integral<T>::value
    205                    && (std::is_same<T, uint8_t>::value
    206                        || std::is_same<T, uint16_t>::value
    207                        || std::is_same<T, uint32_t>::value
    208                        || std::is_same<T, uint64_t>::value)),
    209         /** true if T is one of the fixed length signed or unsigned types */
    210         value = value_i || value_u
    211     };
    212 };
    213 
    214 
    215 //-----------------------------------------------------------------------------
    216 //-----------------------------------------------------------------------------
    217 //-----------------------------------------------------------------------------
    218 
    219 #ifdef _MSC_VER
    220 #   pragma warning(push)
    221 #elif defined(__clang__)
    222 #   pragma clang diagnostic push
    223 #elif defined(__GNUC__)
    224 #   pragma GCC diagnostic push
    225 #   pragma GCC diagnostic ignored "-Wconversion"
    226 #   if __GNUC__ >= 6
    227 #       pragma GCC diagnostic ignored "-Wnull-dereference"
    228 #   endif
    229 #endif
    230 
    231 namespace detail {
    232 
    233 /* python command to get the values below:
    234 def dec(v):
    235     return str(v)
    236 for bits in (8, 16, 32, 64):
    237     imin, imax, umax = (-(1 << (bits - 1))), (1 << (bits - 1)) - 1, (1 << bits) - 1
    238     for vname, v in (("imin", imin), ("imax", imax), ("umax", umax)):
    239         for f in (bin, oct, dec, hex):
    240             print(f"{bits}b: {vname}={v} {f.__name__}: len={len(f(v)):2d}: {v} {f(v)}")
    241 */
    242 
    243 // do not use the type as the template argument because in some
    244 // platforms long!=int32 and long!=int64. Just use the numbytes
    245 // which is more generic and spares lengthy SFINAE code.
    246 template<size_t num_bytes, bool is_signed> struct charconv_digits_;
    247 template<class T> using charconv_digits = charconv_digits_<sizeof(T), std::is_signed<T>::value>;
    248 
    249 template<> struct charconv_digits_<1u, true> // int8_t
    250 {
    251     enum : size_t {
    252         maxdigits_bin       = 1 + 2 + 8, // -128==-0b10000000
    253         maxdigits_oct       = 1 + 2 + 3, // -128==-0o200
    254         maxdigits_dec       = 1     + 3, // -128
    255         maxdigits_hex       = 1 + 2 + 2, // -128==-0x80
    256         maxdigits_bin_nopfx =         8, // -128==-0b10000000
    257         maxdigits_oct_nopfx =         3, // -128==-0o200
    258         maxdigits_dec_nopfx =         3, // -128
    259         maxdigits_hex_nopfx =         2, // -128==-0x80
    260     };
    261     // min values without sign!
    262     static constexpr csubstr min_value_dec() noexcept { return csubstr("128"); }
    263     static constexpr csubstr min_value_hex() noexcept { return csubstr("80"); }
    264     static constexpr csubstr min_value_oct() noexcept { return csubstr("200"); }
    265     static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000"); }
    266     static constexpr csubstr max_value_dec() noexcept { return csubstr("127"); }
    267     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '1')); }
    268 };
    269 template<> struct charconv_digits_<1u, false> // uint8_t
    270 {
    271     enum : size_t {
    272         maxdigits_bin       = 2 + 8, // 255 0b11111111
    273         maxdigits_oct       = 2 + 3, // 255 0o377
    274         maxdigits_dec       =     3, // 255
    275         maxdigits_hex       = 2 + 2, // 255 0xff
    276         maxdigits_bin_nopfx =     8, // 255 0b11111111
    277         maxdigits_oct_nopfx =     3, // 255 0o377
    278         maxdigits_dec_nopfx =     3, // 255
    279         maxdigits_hex_nopfx =     2, // 255 0xff
    280     };
    281     static constexpr csubstr max_value_dec() noexcept { return csubstr("255"); }
    282     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '3')); }
    283 };
    284 template<> struct charconv_digits_<2u, true> // int16_t
    285 {
    286     enum : size_t {
    287         maxdigits_bin       = 1 + 2 + 16, // -32768 -0b1000000000000000
    288         maxdigits_oct       = 1 + 2 +  6, // -32768 -0o100000
    289         maxdigits_dec       = 1     +  5, // -32768 -32768
    290         maxdigits_hex       = 1 + 2 +  4, // -32768 -0x8000
    291         maxdigits_bin_nopfx =         16, // -32768 -0b1000000000000000
    292         maxdigits_oct_nopfx =          6, // -32768 -0o100000
    293         maxdigits_dec_nopfx =          5, // -32768 -32768
    294         maxdigits_hex_nopfx =          4, // -32768 -0x8000
    295     };
    296     // min values without sign!
    297     static constexpr csubstr min_value_dec() noexcept { return csubstr("32768"); }
    298     static constexpr csubstr min_value_hex() noexcept { return csubstr("8000"); }
    299     static constexpr csubstr min_value_oct() noexcept { return csubstr("100000"); }
    300     static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000"); }
    301     static constexpr csubstr max_value_dec() noexcept { return csubstr("32767"); }
    302     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 6)); }
    303 };
    304 template<> struct charconv_digits_<2u, false> // uint16_t
    305 {
    306     enum : size_t {
    307         maxdigits_bin       = 2 + 16, // 65535 0b1111111111111111
    308         maxdigits_oct       = 2 +  6, // 65535 0o177777
    309         maxdigits_dec       =      6, // 65535 65535
    310         maxdigits_hex       = 2 +  4, // 65535 0xffff
    311         maxdigits_bin_nopfx =     16, // 65535 0b1111111111111111
    312         maxdigits_oct_nopfx =      6, // 65535 0o177777
    313         maxdigits_dec_nopfx =      6, // 65535 65535
    314         maxdigits_hex_nopfx =      4, // 65535 0xffff
    315     };
    316     static constexpr csubstr max_value_dec() noexcept { return csubstr("65535"); }
    317     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 6) || (str.len == 6 && str[0] <= '1')); }
    318 };
    319 template<> struct charconv_digits_<4u, true> // int32_t
    320 {
    321     enum : size_t {
    322         maxdigits_bin       = 1 + 2 + 32, // len=35: -2147483648 -0b10000000000000000000000000000000
    323         maxdigits_oct       = 1 + 2 + 11, // len=14: -2147483648 -0o20000000000
    324         maxdigits_dec       = 1     + 10, // len=11: -2147483648 -2147483648
    325         maxdigits_hex       = 1 + 2 +  8, // len=11: -2147483648 -0x80000000
    326         maxdigits_bin_nopfx =         32, // len=35: -2147483648 -0b10000000000000000000000000000000
    327         maxdigits_oct_nopfx =         11, // len=14: -2147483648 -0o20000000000
    328         maxdigits_dec_nopfx =         10, // len=11: -2147483648 -2147483648
    329         maxdigits_hex_nopfx =          8, // len=11: -2147483648 -0x80000000
    330     };
    331     // min values without sign!
    332     static constexpr csubstr min_value_dec() noexcept { return csubstr("2147483648"); }
    333     static constexpr csubstr min_value_hex() noexcept { return csubstr("80000000"); }
    334     static constexpr csubstr min_value_oct() noexcept { return csubstr("20000000000"); }
    335     static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000000000000000000000000000"); }
    336     static constexpr csubstr max_value_dec() noexcept { return csubstr("2147483647"); }
    337     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '1')); }
    338 };
    339 template<> struct charconv_digits_<4u, false> // uint32_t
    340 {
    341     enum : size_t {
    342         maxdigits_bin       = 2 + 32, // len=34: 4294967295 0b11111111111111111111111111111111
    343         maxdigits_oct       = 2 + 11, // len=13: 4294967295 0o37777777777
    344         maxdigits_dec       =     10, // len=10: 4294967295 4294967295
    345         maxdigits_hex       = 2 +  8, // len=10: 4294967295 0xffffffff
    346         maxdigits_bin_nopfx =     32, // len=34: 4294967295 0b11111111111111111111111111111111
    347         maxdigits_oct_nopfx =     11, // len=13: 4294967295 0o37777777777
    348         maxdigits_dec_nopfx =     10, // len=10: 4294967295 4294967295
    349         maxdigits_hex_nopfx =      8, // len=10: 4294967295 0xffffffff
    350     };
    351     static constexpr csubstr max_value_dec() noexcept { return csubstr("4294967295"); }
    352     static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '3')); }
    353 };
    354 template<> struct charconv_digits_<8u, true> // int32_t
    355 {
    356     enum : size_t {
    357         maxdigits_bin       = 1 + 2 + 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000
    358         maxdigits_oct       = 1 + 2 + 22, // len=25: -9223372036854775808 -0o1000000000000000000000
    359         maxdigits_dec       = 1     + 19, // len=20: -9223372036854775808 -9223372036854775808
    360         maxdigits_hex       = 1 + 2 + 16, // len=19: -9223372036854775808 -0x8000000000000000
    361         maxdigits_bin_nopfx =         64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000
    362         maxdigits_oct_nopfx =         22, // len=25: -9223372036854775808 -0o1000000000000000000000
    363         maxdigits_dec_nopfx =         19, // len=20: -9223372036854775808 -9223372036854775808
    364         maxdigits_hex_nopfx =         16, // len=19: -9223372036854775808 -0x8000000000000000
    365     };
    366     static constexpr csubstr min_value_dec() noexcept { return csubstr("9223372036854775808"); }
    367     static constexpr csubstr min_value_hex() noexcept { return csubstr("8000000000000000"); }
    368     static constexpr csubstr min_value_oct() noexcept { return csubstr("1000000000000000000000"); }
    369     static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); }
    370     static constexpr csubstr max_value_dec() noexcept { return csubstr("9223372036854775807"); }
    371     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 22)); }
    372 };
    373 template<> struct charconv_digits_<8u, false>
    374 {
    375     enum : size_t {
    376         maxdigits_bin       = 2 + 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111
    377         maxdigits_oct       = 2 + 22, // len=24: 18446744073709551615 0o1777777777777777777777
    378         maxdigits_dec       =     20, // len=20: 18446744073709551615 18446744073709551615
    379         maxdigits_hex       = 2 + 16, // len=18: 18446744073709551615 0xffffffffffffffff
    380         maxdigits_bin_nopfx =     64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111
    381         maxdigits_oct_nopfx =     22, // len=24: 18446744073709551615 0o1777777777777777777777
    382         maxdigits_dec_nopfx =     20, // len=20: 18446744073709551615 18446744073709551615
    383         maxdigits_hex_nopfx =     16, // len=18: 18446744073709551615 0xffffffffffffffff
    384     };
    385     static constexpr csubstr max_value_dec() noexcept { return csubstr("18446744073709551615"); }
    386     static constexpr bool    is_oct_overflow(csubstr str) noexcept { return !((str.len < 22) || (str.len == 22 && str[0] <= '1')); }
    387 };
    388 } // namespace detail
    389 
    390 
    391 //-----------------------------------------------------------------------------
    392 //-----------------------------------------------------------------------------
    393 //-----------------------------------------------------------------------------
    394 
    395 // Helper macros, undefined below
    396 #define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast<char>(c); } else { ++pos; } }
    397 #define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } }
    398 
    399 /** @name digits_dec return the number of digits required to encode a
    400  * decimal number.
    401  *
    402  * @note At first sight this code may look heavily branchy and
    403  * therefore inefficient. However, measurements revealed this to be
    404  * the fastest among the alternatives.
    405  *
    406  * @see https://github.com/biojppm/c4core/pull/77 */
    407 /** @{ */
    408 
    409 template<class T>
    410 C4_CONSTEXPR14 C4_ALWAYS_INLINE
    411 auto digits_dec(T v) noexcept
    412     -> typename std::enable_if<sizeof(T) == 1u, unsigned>::type
    413 {
    414     C4_STATIC_ASSERT(std::is_integral<T>::value);
    415     C4_ASSERT(v >= 0);
    416     return ((v >= 100) ? 3u : ((v >= 10) ? 2u : 1u));
    417 }
    418 
    419 template<class T>
    420 C4_CONSTEXPR14 C4_ALWAYS_INLINE
    421 auto digits_dec(T v) noexcept
    422     -> typename std::enable_if<sizeof(T) == 2u, unsigned>::type
    423 {
    424     C4_STATIC_ASSERT(std::is_integral<T>::value);
    425     C4_ASSERT(v >= 0);
    426     return ((v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u);
    427 }
    428 
    429 template<class T>
    430 C4_CONSTEXPR14 C4_ALWAYS_INLINE
    431 auto digits_dec(T v) noexcept
    432     -> typename std::enable_if<sizeof(T) == 4u, unsigned>::type
    433 {
    434     C4_STATIC_ASSERT(std::is_integral<T>::value);
    435     C4_ASSERT(v >= 0);
    436     return ((v >= 1000000000) ? 10u : (v >= 100000000) ? 9u : (v >= 10000000) ? 8u :
    437             (v >= 1000000) ? 7u : (v >= 100000) ? 6u : (v >= 10000) ? 5u :
    438             (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u);
    439 }
    440 
    441 template<class T>
    442 C4_CONSTEXPR14 C4_ALWAYS_INLINE
    443 auto digits_dec(T v) noexcept
    444     -> typename std::enable_if<sizeof(T) == 8u, unsigned>::type
    445 {
    446     // thanks @fargies!!!
    447     // https://github.com/biojppm/c4core/pull/77#issuecomment-1063753568
    448     C4_STATIC_ASSERT(std::is_integral<T>::value);
    449     C4_ASSERT(v >= 0);
    450     if(v >= 1000000000) // 10
    451     {
    452         if(v >= 100000000000000) // 15 [15-20] range
    453         {
    454             if(v >= 100000000000000000) // 18 (15 + (20 - 15) / 2)
    455             {
    456                 if((typename std::make_unsigned<T>::type)v >= 10000000000000000000u) // 20
    457                     return 20u;
    458                 else
    459                     return (v >= 1000000000000000000) ? 19u : 18u;
    460             }
    461             else if(v >= 10000000000000000) // 17
    462                 return 17u;
    463             else
    464                 return(v >= 1000000000000000) ? 16u : 15u;
    465         }
    466         else if(v >= 1000000000000) // 13
    467             return (v >= 10000000000000) ? 14u : 13u;
    468         else if(v >= 100000000000) // 12
    469             return 12;
    470         else
    471             return(v >= 10000000000) ? 11u : 10u;
    472     }
    473     else if(v >= 10000) // 5 [5-9] range
    474     {
    475         if(v >= 10000000) // 8
    476             return (v >= 100000000) ? 9u : 8u;
    477         else if(v >= 1000000) // 7
    478             return 7;
    479         else
    480             return (v >= 100000) ? 6u : 5u;
    481     }
    482     else if(v >= 100)
    483         return (v >= 1000) ? 4u : 3u;
    484     else
    485         return (v >= 10) ? 2u : 1u;
    486 }
    487 
    488 /** @} */
    489 
    490 
    491 template<class T>
    492 C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_hex(T v) noexcept
    493 {
    494     C4_STATIC_ASSERT(std::is_integral<T>::value);
    495     C4_ASSERT(v >= 0);
    496     return v ? 1u + (msb((typename std::make_unsigned<T>::type)v) >> 2u) : 1u;
    497 }
    498 
    499 template<class T>
    500 C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_bin(T v) noexcept
    501 {
    502     C4_STATIC_ASSERT(std::is_integral<T>::value);
    503     C4_ASSERT(v >= 0);
    504     return v ? 1u + msb((typename std::make_unsigned<T>::type)v) : 1u;
    505 }
    506 
    507 template<class T>
    508 C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_oct(T v_) noexcept
    509 {
    510     // TODO: is there a better way?
    511     C4_STATIC_ASSERT(std::is_integral<T>::value);
    512     C4_ASSERT(v_ >= 0);
    513     using U = typename
    514         std::conditional<sizeof(T) <= sizeof(unsigned),
    515                          unsigned,
    516                          typename std::make_unsigned<T>::type>::type;
    517     U v = (U) v_;  // safe because we require v_ >= 0
    518     unsigned __n = 1;
    519     const unsigned __b2 = 64u;
    520     const unsigned __b3 = __b2 * 8u;
    521     const unsigned long __b4 = __b3 * 8u;
    522     while(true)
    523 	{
    524         if(v < 8u)
    525             return __n;
    526         if(v < __b2)
    527             return __n + 1;
    528         if(v < __b3)
    529             return __n + 2;
    530         if(v < __b4)
    531             return __n + 3;
    532         v /= (U) __b4;
    533         __n += 4;
    534 	}
    535 }
    536 
    537 
    538 //-----------------------------------------------------------------------------
    539 //-----------------------------------------------------------------------------
    540 //-----------------------------------------------------------------------------
    541 
    542 namespace detail {
    543 C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef";
    544 C4_INLINE_CONSTEXPR const char digits0099[] =
    545     "0001020304050607080910111213141516171819"
    546     "2021222324252627282930313233343536373839"
    547     "4041424344454647484950515253545556575859"
    548     "6061626364656667686970717273747576777879"
    549     "8081828384858687888990919293949596979899";
    550 } // namespace detail
    551 
    552 C4_SUPPRESS_WARNING_GCC_PUSH
    553 C4_SUPPRESS_WARNING_GCC("-Warray-bounds")  // gcc has false positives here
    554 #if (defined(__GNUC__) && (__GNUC__ >= 7))
    555 C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow")  // gcc has false positives here
    556 #endif
    557 
    558 template<class T>
    559 C4_HOT C4_ALWAYS_INLINE
    560 void write_dec_unchecked(substr buf, T v, unsigned digits_v) noexcept
    561 {
    562     C4_STATIC_ASSERT(std::is_integral<T>::value);
    563     C4_ASSERT(v >= 0);
    564     C4_ASSERT(buf.len >= digits_v);
    565     C4_XASSERT(digits_v == digits_dec(v));
    566     // in bm_xtoa: checkoncelog_singlediv_write2
    567     while(v >= T(100))
    568     {
    569         T quo = v;
    570         quo /= T(100);
    571         const auto num = (v - quo * T(100)) << 1u;
    572         v = quo;
    573         buf.str[--digits_v] = detail::digits0099[num + 1];
    574         buf.str[--digits_v] = detail::digits0099[num];
    575     }
    576     if(v >= T(10))
    577     {
    578         C4_ASSERT(digits_v == 2);
    579         const auto num = v << 1u;
    580         buf.str[1] = detail::digits0099[num + 1];
    581         buf.str[0] = detail::digits0099[num];
    582     }
    583     else
    584     {
    585         C4_ASSERT(digits_v == 1);
    586         buf.str[0] = (char)('0' + v);
    587     }
    588 }
    589 
    590 
    591 template<class T>
    592 C4_HOT C4_ALWAYS_INLINE
    593 void write_hex_unchecked(substr buf, T v, unsigned digits_v) noexcept
    594 {
    595     C4_STATIC_ASSERT(std::is_integral<T>::value);
    596     C4_ASSERT(v >= 0);
    597     C4_ASSERT(buf.len >= digits_v);
    598     C4_XASSERT(digits_v == digits_hex(v));
    599     do {
    600         buf.str[--digits_v] = detail::hexchars[v & T(15)];
    601         v >>= 4;
    602     } while(v);
    603     C4_ASSERT(digits_v == 0);
    604 }
    605 
    606 
    607 template<class T>
    608 C4_HOT C4_ALWAYS_INLINE
    609 void write_oct_unchecked(substr buf, T v, unsigned digits_v) noexcept
    610 {
    611     C4_STATIC_ASSERT(std::is_integral<T>::value);
    612     C4_ASSERT(v >= 0);
    613     C4_ASSERT(buf.len >= digits_v);
    614     C4_XASSERT(digits_v == digits_oct(v));
    615     do {
    616         buf.str[--digits_v] = (char)('0' + (v & T(7)));
    617         v >>= 3;
    618     } while(v);
    619     C4_ASSERT(digits_v == 0);
    620 }
    621 
    622 
    623 template<class T>
    624 C4_HOT C4_ALWAYS_INLINE
    625 void write_bin_unchecked(substr buf, T v, unsigned digits_v) noexcept
    626 {
    627     C4_STATIC_ASSERT(std::is_integral<T>::value);
    628     C4_ASSERT(v >= 0);
    629     C4_ASSERT(buf.len >= digits_v);
    630     C4_XASSERT(digits_v == digits_bin(v));
    631     do {
    632         buf.str[--digits_v] = (char)('0' + (v & T(1)));
    633         v >>= 1;
    634     } while(v);
    635     C4_ASSERT(digits_v == 0);
    636 }
    637 
    638 
    639 /** write an integer to a string in decimal format. This is the
    640  * lowest level (and the fastest) function to do this task.
    641  * @note does not accept negative numbers
    642  * @note the resulting string is NOT zero-terminated.
    643  * @note it is ok to call this with an empty or too-small buffer;
    644  * no writes will occur, and the required size will be returned
    645  * @return the number of characters required for the buffer. */
    646 template<class T>
    647 C4_ALWAYS_INLINE size_t write_dec(substr buf, T v) noexcept
    648 {
    649     C4_STATIC_ASSERT(std::is_integral<T>::value);
    650     C4_ASSERT(v >= 0);
    651     unsigned digits = digits_dec(v);
    652     if(C4_LIKELY(buf.len >= digits))
    653         write_dec_unchecked(buf, v, digits);
    654     return digits;
    655 }
    656 
    657 /** write an integer to a string in hexadecimal format. This is the
    658  * lowest level (and the fastest) function to do this task.
    659  * @note does not accept negative numbers
    660  * @note does not prefix with 0x
    661  * @note the resulting string is NOT zero-terminated.
    662  * @note it is ok to call this with an empty or too-small buffer;
    663  * no writes will occur, and the required size will be returned
    664  * @return the number of characters required for the buffer. */
    665 template<class T>
    666 C4_ALWAYS_INLINE size_t write_hex(substr buf, T v) noexcept
    667 {
    668     C4_STATIC_ASSERT(std::is_integral<T>::value);
    669     C4_ASSERT(v >= 0);
    670     unsigned digits = digits_hex(v);
    671     if(C4_LIKELY(buf.len >= digits))
    672         write_hex_unchecked(buf, v, digits);
    673     return digits;
    674 }
    675 
    676 /** write an integer to a string in octal format. This is the
    677  * lowest level (and the fastest) function to do this task.
    678  * @note does not accept negative numbers
    679  * @note does not prefix with 0o
    680  * @note the resulting string is NOT zero-terminated.
    681  * @note it is ok to call this with an empty or too-small buffer;
    682  * no writes will occur, and the required size will be returned
    683  * @return the number of characters required for the buffer. */
    684 template<class T>
    685 C4_ALWAYS_INLINE size_t write_oct(substr buf, T v) noexcept
    686 {
    687     C4_STATIC_ASSERT(std::is_integral<T>::value);
    688     C4_ASSERT(v >= 0);
    689     unsigned digits = digits_oct(v);
    690     if(C4_LIKELY(buf.len >= digits))
    691         write_oct_unchecked(buf, v, digits);
    692     return digits;
    693 }
    694 
    695 /** write an integer to a string in binary format. This is the
    696  * lowest level (and the fastest) function to do this task.
    697  * @note does not accept negative numbers
    698  * @note does not prefix with 0b
    699  * @note the resulting string is NOT zero-terminated.
    700  * @note it is ok to call this with an empty or too-small buffer;
    701  * no writes will occur, and the required size will be returned
    702  * @return the number of characters required for the buffer. */
    703 template<class T>
    704 C4_ALWAYS_INLINE size_t write_bin(substr buf, T v) noexcept
    705 {
    706     C4_STATIC_ASSERT(std::is_integral<T>::value);
    707     C4_ASSERT(v >= 0);
    708     unsigned digits = digits_bin(v);
    709     C4_ASSERT(digits > 0);
    710     if(C4_LIKELY(buf.len >= digits))
    711         write_bin_unchecked(buf, v, digits);
    712     return digits;
    713 }
    714 
    715 
    716 namespace detail {
    717 template<class U> using NumberWriter = size_t (*)(substr, U);
    718 template<class T, NumberWriter<T> writer>
    719 size_t write_num_digits(substr buf, T v, size_t num_digits) noexcept
    720 {
    721     C4_STATIC_ASSERT(std::is_integral<T>::value);
    722     size_t ret = writer(buf, v);
    723     if(ret >= num_digits)
    724         return ret;
    725     else if(ret >= buf.len || num_digits > buf.len)
    726         return num_digits;
    727     C4_ASSERT(num_digits >= ret);
    728     size_t delta = static_cast<size_t>(num_digits - ret);
    729     memmove(buf.str + delta, buf.str, ret);
    730     memset(buf.str, '0', delta);
    731     return num_digits;
    732 }
    733 } // namespace detail
    734 
    735 
    736 /** same as c4::write_dec(), but pad with zeroes on the left
    737  * such that the resulting string is @p num_digits wide.
    738  * If the given number is requires more than num_digits, then the number prevails. */
    739 template<class T>
    740 C4_ALWAYS_INLINE size_t write_dec(substr buf, T val, size_t num_digits) noexcept
    741 {
    742     return detail::write_num_digits<T, &write_dec<T>>(buf, val, num_digits);
    743 }
    744 
    745 /** same as c4::write_hex(), but pad with zeroes on the left
    746  * such that the resulting string is @p num_digits wide.
    747  * If the given number is requires more than num_digits, then the number prevails. */
    748 template<class T>
    749 C4_ALWAYS_INLINE size_t write_hex(substr buf, T val, size_t num_digits) noexcept
    750 {
    751     return detail::write_num_digits<T, &write_hex<T>>(buf, val, num_digits);
    752 }
    753 
    754 /** same as c4::write_bin(), but pad with zeroes on the left
    755  * such that the resulting string is @p num_digits wide.
    756  * If the given number is requires more than num_digits, then the number prevails. */
    757 template<class T>
    758 C4_ALWAYS_INLINE size_t write_bin(substr buf, T val, size_t num_digits) noexcept
    759 {
    760     return detail::write_num_digits<T, &write_bin<T>>(buf, val, num_digits);
    761 }
    762 
    763 /** same as c4::write_oct(), but pad with zeroes on the left
    764  * such that the resulting string is @p num_digits wide.
    765  * If the given number is requires more than num_digits, then the number prevails. */
    766 template<class T>
    767 C4_ALWAYS_INLINE size_t write_oct(substr buf, T val, size_t num_digits) noexcept
    768 {
    769     return detail::write_num_digits<T, &write_oct<T>>(buf, val, num_digits);
    770 }
    771 
    772 C4_SUPPRESS_WARNING_GCC_POP
    773 
    774 
    775 //-----------------------------------------------------------------------------
    776 //-----------------------------------------------------------------------------
    777 //-----------------------------------------------------------------------------
    778 
    779 
    780 C4_SUPPRESS_WARNING_MSVC_PUSH
    781 C4_SUPPRESS_WARNING_MSVC(4365) // '=': conversion from 'int' to 'I', signed/unsigned mismatch
    782 
    783 /** read a decimal integer from a string. This is the
    784  * lowest level (and the fastest) function to do this task.
    785  * @note does not accept negative numbers
    786  * @note The string must be trimmed. Whitespace is not accepted.
    787  * @note the string must not be empty
    788  * @note there is no check for overflow; the value wraps around
    789  * in a way similar to the standard C/C++ overflow behavior.
    790  * For example, `read_dec<int8_t>("128", &val)` returns true
    791  * and val will be set to 0 because 127 is the max i8 value.
    792  * @see overflows<T>() to find out if a number string overflows a type range
    793  * @return true if the conversion was successful (no overflow check) */
    794 template<class I>
    795 C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v) noexcept
    796 {
    797     C4_STATIC_ASSERT(std::is_integral<I>::value);
    798     C4_ASSERT(!s.empty());
    799     *v = 0;
    800     for(char c : s)
    801     {
    802         if(C4_UNLIKELY(c < '0' || c > '9'))
    803             return false;
    804         *v = (*v) * I(10) + (I(c) - I('0'));
    805     }
    806     return true;
    807 }
    808 
    809 /** read an hexadecimal integer from a string. This is the
    810  * lowest level (and the fastest) function to do this task.
    811  * @note does not accept negative numbers
    812  * @note does not accept leading 0x or 0X
    813  * @note the string must not be empty
    814  * @note the string must be trimmed. Whitespace is not accepted.
    815  * @note there is no check for overflow; the value wraps around
    816  * in a way similar to the standard C/C++ overflow behavior.
    817  * For example, `read_hex<int8_t>("80", &val)` returns true
    818  * and val will be set to 0 because 7f is the max i8 value.
    819  * @see overflows<T>() to find out if a number string overflows a type range
    820  * @return true if the conversion was successful (no overflow check) */
    821 template<class I>
    822 C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v) noexcept
    823 {
    824     C4_STATIC_ASSERT(std::is_integral<I>::value);
    825     C4_ASSERT(!s.empty());
    826     *v = 0;
    827     for(char c : s)
    828     {
    829         I cv;
    830         if(c >= '0' && c <= '9')
    831             cv = I(c) - I('0');
    832         else if(c >= 'a' && c <= 'f')
    833             cv = I(10) + (I(c) - I('a'));
    834         else if(c >= 'A' && c <= 'F')
    835             cv = I(10) + (I(c) - I('A'));
    836         else
    837             return false;
    838         *v = (*v) * I(16) + cv;
    839     }
    840     return true;
    841 }
    842 
    843 /** read a binary integer from a string. This is the
    844  * lowest level (and the fastest) function to do this task.
    845  * @note does not accept negative numbers
    846  * @note does not accept leading 0b or 0B
    847  * @note the string must not be empty
    848  * @note the string must be trimmed. Whitespace is not accepted.
    849  * @note there is no check for overflow; the value wraps around
    850  * in a way similar to the standard C/C++ overflow behavior.
    851  * For example, `read_bin<int8_t>("10000000", &val)` returns true
    852  * and val will be set to 0 because 1111111 is the max i8 value.
    853  * @see overflows<T>() to find out if a number string overflows a type range
    854  * @return true if the conversion was successful (no overflow check) */
    855 template<class I>
    856 C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v) noexcept
    857 {
    858     C4_STATIC_ASSERT(std::is_integral<I>::value);
    859     C4_ASSERT(!s.empty());
    860     *v = 0;
    861     for(char c : s)
    862     {
    863         *v <<= 1;
    864         if(c == '1')
    865             *v |= 1;
    866         else if(c != '0')
    867             return false;
    868     }
    869     return true;
    870 }
    871 
    872 /** read an octal integer from a string. This is the
    873  * lowest level (and the fastest) function to do this task.
    874  * @note does not accept negative numbers
    875  * @note does not accept leading 0o or 0O
    876  * @note the string must not be empty
    877  * @note the string must be trimmed. Whitespace is not accepted.
    878  * @note there is no check for overflow; the value wraps around
    879  * in a way similar to the standard C/C++ overflow behavior.
    880  * For example, `read_oct<int8_t>("200", &val)` returns true
    881  * and val will be set to 0 because 177 is the max i8 value.
    882  * @see overflows<T>() to find out if a number string overflows a type range
    883  * @return true if the conversion was successful (no overflow check) */
    884 template<class I>
    885 C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v) noexcept
    886 {
    887     C4_STATIC_ASSERT(std::is_integral<I>::value);
    888     C4_ASSERT(!s.empty());
    889     *v = 0;
    890     for(char c : s)
    891     {
    892         if(C4_UNLIKELY(c < '0' || c > '7'))
    893             return false;
    894         *v = (*v) * I(8) + (I(c) - I('0'));
    895     }
    896     return true;
    897 }
    898 
    899 C4_SUPPRESS_WARNING_MSVC_POP
    900 
    901 
    902 //-----------------------------------------------------------------------------
    903 //-----------------------------------------------------------------------------
    904 //-----------------------------------------------------------------------------
    905 
    906 C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wswitch-default")
    907 
    908 namespace detail {
    909 inline size_t _itoa2buf(substr buf, size_t pos, csubstr val) noexcept
    910 {
    911     C4_ASSERT(pos + val.len <= buf.len);
    912     memcpy(buf.str + pos, val.str, val.len);
    913     return pos + val.len;
    914 }
    915 inline size_t _itoa2bufwithdigits(substr buf, size_t pos, size_t num_digits, csubstr val) noexcept
    916 {
    917     num_digits = num_digits > val.len ? num_digits - val.len : 0;
    918     C4_ASSERT(num_digits + val.len <= buf.len);
    919     for(size_t i = 0; i < num_digits; ++i)
    920         _c4append('0');
    921     return detail::_itoa2buf(buf, pos, val);
    922 }
    923 template<class I>
    924 C4_NO_INLINE size_t _itoadec2buf(substr buf) noexcept
    925 {
    926     using digits_type = detail::charconv_digits<I>;
    927     if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec))
    928         return digits_type::maxdigits_dec;
    929     buf.str[0] = '-';
    930     return detail::_itoa2buf(buf, 1, digits_type::min_value_dec());
    931 }
    932 template<class I>
    933 C4_NO_INLINE size_t _itoa2buf(substr buf, I radix) noexcept
    934 {
    935     using digits_type = detail::charconv_digits<I>;
    936     size_t pos = 0;
    937     if(C4_LIKELY(buf.len > 0))
    938         buf.str[pos++] = '-';
    939     switch(radix)
    940     {
    941     case I(10):
    942         if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec))
    943             return digits_type::maxdigits_dec;
    944         pos =_itoa2buf(buf, pos, digits_type::min_value_dec());
    945         break;
    946     case I(16):
    947         if(C4_UNLIKELY(buf.len < digits_type::maxdigits_hex))
    948             return digits_type::maxdigits_hex;
    949         buf.str[pos++] = '0';
    950         buf.str[pos++] = 'x';
    951         pos = _itoa2buf(buf, pos, digits_type::min_value_hex());
    952         break;
    953     case I( 2):
    954         if(C4_UNLIKELY(buf.len < digits_type::maxdigits_bin))
    955             return digits_type::maxdigits_bin;
    956         buf.str[pos++] = '0';
    957         buf.str[pos++] = 'b';
    958         pos = _itoa2buf(buf, pos, digits_type::min_value_bin());
    959         break;
    960     case I( 8):
    961         if(C4_UNLIKELY(buf.len < digits_type::maxdigits_oct))
    962             return digits_type::maxdigits_oct;
    963         buf.str[pos++] = '0';
    964         buf.str[pos++] = 'o';
    965         pos = _itoa2buf(buf, pos, digits_type::min_value_oct());
    966         break;
    967     }
    968     return pos;
    969 }
    970 template<class I>
    971 C4_NO_INLINE size_t _itoa2buf(substr buf, I radix, size_t num_digits) noexcept
    972 {
    973     using digits_type = detail::charconv_digits<I>;
    974     size_t pos = 0;
    975     size_t needed_digits = 0;
    976     if(C4_LIKELY(buf.len > 0))
    977         buf.str[pos++] = '-';
    978     switch(radix)
    979     {
    980     case I(10):
    981         // add 1 to account for -
    982         needed_digits = num_digits+1 > digits_type::maxdigits_dec ? num_digits+1 : digits_type::maxdigits_dec;
    983         if(C4_UNLIKELY(buf.len < needed_digits))
    984             return needed_digits;
    985         pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_dec());
    986         break;
    987     case I(16):
    988         // add 3 to account for -0x
    989         needed_digits = num_digits+3 > digits_type::maxdigits_hex ? num_digits+3 : digits_type::maxdigits_hex;
    990         if(C4_UNLIKELY(buf.len < needed_digits))
    991             return needed_digits;
    992         buf.str[pos++] = '0';
    993         buf.str[pos++] = 'x';
    994         pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_hex());
    995         break;
    996     case I(2):
    997         // add 3 to account for -0b
    998         needed_digits = num_digits+3 > digits_type::maxdigits_bin ? num_digits+3 : digits_type::maxdigits_bin;
    999         if(C4_UNLIKELY(buf.len < needed_digits))
   1000             return needed_digits;
   1001         C4_ASSERT(buf.len >= digits_type::maxdigits_bin);
   1002         buf.str[pos++] = '0';
   1003         buf.str[pos++] = 'b';
   1004         pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_bin());
   1005         break;
   1006     case I(8):
   1007         // add 3 to account for -0o
   1008         needed_digits = num_digits+3 > digits_type::maxdigits_oct ? num_digits+3 : digits_type::maxdigits_oct;
   1009         if(C4_UNLIKELY(buf.len < needed_digits))
   1010             return needed_digits;
   1011         C4_ASSERT(buf.len >= digits_type::maxdigits_oct);
   1012         buf.str[pos++] = '0';
   1013         buf.str[pos++] = 'o';
   1014         pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_oct());
   1015         break;
   1016     }
   1017     return pos;
   1018 }
   1019 } // namespace detail
   1020 
   1021 
   1022 /** convert an integral signed decimal to a string.
   1023  * @note the resulting string is NOT zero-terminated.
   1024  * @note it is ok to call this with an empty or too-small buffer;
   1025  * no writes will occur, and the needed size will be returned
   1026  * @return the number of characters required for the buffer. */
   1027 template<class T>
   1028 C4_ALWAYS_INLINE size_t itoa(substr buf, T v) noexcept
   1029 {
   1030     C4_STATIC_ASSERT(std::is_signed<T>::value);
   1031     if(v >= T(0))
   1032     {
   1033         // write_dec() checks the buffer size, so no need to check here
   1034         return write_dec(buf, v);
   1035     }
   1036     // when T is the min value (eg i8: -128), negating it
   1037     // will overflow, so treat the min as a special case
   1038     else if(C4_LIKELY(v != std::numeric_limits<T>::min()))
   1039     {
   1040         v = -v;
   1041         unsigned digits = digits_dec(v);
   1042         if(C4_LIKELY(buf.len >= digits + 1u))
   1043         {
   1044             buf.str[0] = '-';
   1045             write_dec_unchecked(buf.sub(1), v, digits);
   1046         }
   1047         return digits + 1u;
   1048     }
   1049     return detail::_itoadec2buf<T>(buf);
   1050 }
   1051 
   1052 /** convert an integral signed integer to a string, using a specific
   1053  * radix. The radix must be 2, 8, 10 or 16.
   1054  *
   1055  * @note the resulting string is NOT zero-terminated.
   1056  * @note it is ok to call this with an empty or too-small buffer;
   1057  * no writes will occur, and the needed size will be returned
   1058  * @return the number of characters required for the buffer. */
   1059 template<class T>
   1060 C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix) noexcept
   1061 {
   1062     C4_STATIC_ASSERT(std::is_signed<T>::value);
   1063     C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16);
   1064     C4_SUPPRESS_WARNING_GCC_PUSH
   1065     #if (defined(__GNUC__) && (__GNUC__ >= 7))
   1066         C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow")  // gcc has a false positive here
   1067     #endif
   1068     // when T is the min value (eg i8: -128), negating it
   1069     // will overflow, so treat the min as a special case
   1070     if(C4_LIKELY(v != std::numeric_limits<T>::min()))
   1071     {
   1072         unsigned pos = 0;
   1073         if(v < 0)
   1074         {
   1075             v = -v;
   1076             if(C4_LIKELY(buf.len > 0))
   1077                 buf.str[pos] = '-';
   1078             ++pos;
   1079         }
   1080         unsigned digits = 0;
   1081         switch(radix)
   1082         {
   1083         case T(10):
   1084             digits = digits_dec(v);
   1085             if(C4_LIKELY(buf.len >= pos + digits))
   1086                 write_dec_unchecked(buf.sub(pos), v, digits);
   1087             break;
   1088         case T(16):
   1089             digits = digits_hex(v);
   1090             if(C4_LIKELY(buf.len >= pos + 2u + digits))
   1091             {
   1092                 buf.str[pos + 0] = '0';
   1093                 buf.str[pos + 1] = 'x';
   1094                 write_hex_unchecked(buf.sub(pos + 2), v, digits);
   1095             }
   1096             digits += 2u;
   1097             break;
   1098         case T(2):
   1099             digits = digits_bin(v);
   1100             if(C4_LIKELY(buf.len >= pos + 2u + digits))
   1101             {
   1102                 buf.str[pos + 0] = '0';
   1103                 buf.str[pos + 1] = 'b';
   1104                 write_bin_unchecked(buf.sub(pos + 2), v, digits);
   1105             }
   1106             digits += 2u;
   1107             break;
   1108         case T(8):
   1109             digits = digits_oct(v);
   1110             if(C4_LIKELY(buf.len >= pos + 2u + digits))
   1111             {
   1112                 buf.str[pos + 0] = '0';
   1113                 buf.str[pos + 1] = 'o';
   1114                 write_oct_unchecked(buf.sub(pos + 2), v, digits);
   1115             }
   1116             digits += 2u;
   1117             break;
   1118         }
   1119         return pos + digits;
   1120     }
   1121     C4_SUPPRESS_WARNING_GCC_POP
   1122     // when T is the min value (eg i8: -128), negating it
   1123     // will overflow
   1124     return detail::_itoa2buf<T>(buf, radix);
   1125 }
   1126 
   1127 
   1128 /** same as c4::itoa(), but pad with zeroes on the left such that the
   1129  * resulting string is @p num_digits wide, not accounting for radix
   1130  * prefix (0x,0o,0b). The @p radix must be 2, 8, 10 or 16.
   1131  *
   1132  * @note the resulting string is NOT zero-terminated.
   1133  * @note it is ok to call this with an empty or too-small buffer;
   1134  * no writes will occur, and the needed size will be returned
   1135  * @return the number of characters required for the buffer. */
   1136 template<class T>
   1137 C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix, size_t num_digits) noexcept
   1138 {
   1139     C4_STATIC_ASSERT(std::is_signed<T>::value);
   1140     C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16);
   1141     C4_SUPPRESS_WARNING_GCC_PUSH
   1142     #if (defined(__GNUC__) && (__GNUC__ >= 7))
   1143         C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow")  // gcc has a false positive here
   1144     #endif
   1145     // when T is the min value (eg i8: -128), negating it
   1146     // will overflow, so treat the min as a special case
   1147     if(C4_LIKELY(v != std::numeric_limits<T>::min()))
   1148     {
   1149         unsigned pos = 0;
   1150         if(v < 0)
   1151         {
   1152             v = -v;
   1153             if(C4_LIKELY(buf.len > 0))
   1154                 buf.str[pos] = '-';
   1155             ++pos;
   1156         }
   1157         unsigned total_digits = 0;
   1158         switch(radix)
   1159         {
   1160         case T(10):
   1161             total_digits = digits_dec(v);
   1162             total_digits = pos + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
   1163             if(C4_LIKELY(buf.len >= total_digits))
   1164                 write_dec(buf.sub(pos), v, num_digits);
   1165             break;
   1166         case T(16):
   1167             total_digits = digits_hex(v);
   1168             total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
   1169             if(C4_LIKELY(buf.len >= total_digits))
   1170             {
   1171                 buf.str[pos + 0] = '0';
   1172                 buf.str[pos + 1] = 'x';
   1173                 write_hex(buf.sub(pos + 2), v, num_digits);
   1174             }
   1175             break;
   1176         case T(2):
   1177             total_digits = digits_bin(v);
   1178             total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
   1179             if(C4_LIKELY(buf.len >= total_digits))
   1180             {
   1181                 buf.str[pos + 0] = '0';
   1182                 buf.str[pos + 1] = 'b';
   1183                 write_bin(buf.sub(pos + 2), v, num_digits);
   1184             }
   1185             break;
   1186         case T(8):
   1187             total_digits = digits_oct(v);
   1188             total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
   1189             if(C4_LIKELY(buf.len >= total_digits))
   1190             {
   1191                 buf.str[pos + 0] = '0';
   1192                 buf.str[pos + 1] = 'o';
   1193                 write_oct(buf.sub(pos + 2), v, num_digits);
   1194             }
   1195             break;
   1196         }
   1197         return total_digits;
   1198     }
   1199     C4_SUPPRESS_WARNING_GCC_POP
   1200     // when T is the min value (eg i8: -128), negating it
   1201     // will overflow
   1202     return detail::_itoa2buf<T>(buf, radix, num_digits);
   1203 }
   1204 
   1205 
   1206 //-----------------------------------------------------------------------------
   1207 //-----------------------------------------------------------------------------
   1208 //-----------------------------------------------------------------------------
   1209 
   1210 /** convert an integral unsigned decimal to a string.
   1211  *
   1212  * @note the resulting string is NOT zero-terminated.
   1213  * @note it is ok to call this with an empty or too-small buffer;
   1214  * no writes will occur, and the needed size will be returned
   1215  * @return the number of characters required for the buffer. */
   1216 template<class T>
   1217 C4_ALWAYS_INLINE size_t utoa(substr buf, T v) noexcept
   1218 {
   1219     C4_STATIC_ASSERT(std::is_unsigned<T>::value);
   1220     // write_dec() does the buffer length check, so no need to check here
   1221     return write_dec(buf, v);
   1222 }
   1223 
   1224 /** convert an integral unsigned integer to a string, using a specific
   1225  * radix. The radix must be 2, 8, 10 or 16.
   1226  *
   1227  * @note the resulting string is NOT zero-terminated.
   1228  * @note it is ok to call this with an empty or too-small buffer;
   1229  * no writes will occur, and the needed size will be returned
   1230  * @return the number of characters required for the buffer. */
   1231 template<class T>
   1232 C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix) noexcept
   1233 {
   1234     C4_STATIC_ASSERT(std::is_unsigned<T>::value);
   1235     C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8);
   1236     unsigned digits = 0;
   1237     switch(radix)
   1238     {
   1239     case T(10):
   1240         digits = digits_dec(v);
   1241         if(C4_LIKELY(buf.len >= digits))
   1242             write_dec_unchecked(buf, v, digits);
   1243         break;
   1244     case T(16):
   1245         digits = digits_hex(v);
   1246         if(C4_LIKELY(buf.len >= digits+2u))
   1247         {
   1248             buf.str[0] = '0';
   1249             buf.str[1] = 'x';
   1250             write_hex_unchecked(buf.sub(2), v, digits);
   1251         }
   1252         digits += 2u;
   1253         break;
   1254     case T(2):
   1255         digits = digits_bin(v);
   1256         if(C4_LIKELY(buf.len >= digits+2u))
   1257         {
   1258             buf.str[0] = '0';
   1259             buf.str[1] = 'b';
   1260             write_bin_unchecked(buf.sub(2), v, digits);
   1261         }
   1262         digits += 2u;
   1263         break;
   1264     case T(8):
   1265         digits = digits_oct(v);
   1266         if(C4_LIKELY(buf.len >= digits+2u))
   1267         {
   1268             buf.str[0] = '0';
   1269             buf.str[1] = 'o';
   1270             write_oct_unchecked(buf.sub(2), v, digits);
   1271         }
   1272         digits += 2u;
   1273         break;
   1274     }
   1275     return digits;
   1276 }
   1277 
   1278 /** same as c4::utoa(), but pad with zeroes on the left such that the
   1279  * resulting string is @p num_digits wide. The @p radix must be 2,
   1280  * 8, 10 or 16.
   1281  *
   1282  * @note the resulting string is NOT zero-terminated.
   1283  * @note it is ok to call this with an empty or too-small buffer;
   1284  * no writes will occur, and the needed size will be returned
   1285  * @return the number of characters required for the buffer. */
   1286 template<class T>
   1287 C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix, size_t num_digits) noexcept
   1288 {
   1289     C4_STATIC_ASSERT(std::is_unsigned<T>::value);
   1290     C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8);
   1291     unsigned total_digits = 0;
   1292     switch(radix)
   1293     {
   1294     case T(10):
   1295         total_digits = digits_dec(v);
   1296         total_digits = (unsigned)(num_digits > total_digits ? num_digits : total_digits);
   1297         if(C4_LIKELY(buf.len >= total_digits))
   1298             write_dec(buf, v, num_digits);
   1299         break;
   1300     case T(16):
   1301         total_digits = digits_hex(v);
   1302         total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
   1303         if(C4_LIKELY(buf.len >= total_digits))
   1304         {
   1305             buf.str[0] = '0';
   1306             buf.str[1] = 'x';
   1307             write_hex(buf.sub(2), v, num_digits);
   1308         }
   1309         break;
   1310     case T(2):
   1311         total_digits = digits_bin(v);
   1312         total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
   1313         if(C4_LIKELY(buf.len >= total_digits))
   1314         {
   1315             buf.str[0] = '0';
   1316             buf.str[1] = 'b';
   1317             write_bin(buf.sub(2), v, num_digits);
   1318         }
   1319         break;
   1320     case T(8):
   1321         total_digits = digits_oct(v);
   1322         total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
   1323         if(C4_LIKELY(buf.len >= total_digits))
   1324         {
   1325             buf.str[0] = '0';
   1326             buf.str[1] = 'o';
   1327             write_oct(buf.sub(2), v, num_digits);
   1328         }
   1329         break;
   1330     }
   1331     return total_digits;
   1332 }
   1333 C4_SUPPRESS_WARNING_GCC_POP
   1334 
   1335 
   1336 //-----------------------------------------------------------------------------
   1337 //-----------------------------------------------------------------------------
   1338 //-----------------------------------------------------------------------------
   1339 
   1340 /** Convert a trimmed string to a signed integral value. The input
   1341  * string can be formatted as decimal, binary (prefix 0b or 0B), octal
   1342  * (prefix 0o or 0O) or hexadecimal (prefix 0x or 0X). Strings with
   1343  * leading zeroes are considered as decimal and not octal (unlike the
   1344  * C/C++ convention). Every character in the input string is read for
   1345  * the conversion; the input string must not contain any leading or
   1346  * trailing whitespace.
   1347  *
   1348  * @return true if the conversion was successful.
   1349  *
   1350  * @note overflow is not detected: the return status is true even if
   1351  * the conversion would return a value outside of the type's range, in
   1352  * which case the result will wrap around the type's range.
   1353  * This is similar to native behavior.
   1354  *
   1355  * @note a positive sign is not accepted. ie, the string must not
   1356  * start with '+'
   1357  *
   1358  * @see atoi_first() if the string is not trimmed to the value to read. */
   1359 template<class T>
   1360 C4_ALWAYS_INLINE bool atoi(csubstr str, T * C4_RESTRICT v) noexcept
   1361 {
   1362     C4_STATIC_ASSERT(std::is_integral<T>::value);
   1363     C4_STATIC_ASSERT(std::is_signed<T>::value);
   1364 
   1365     if(C4_UNLIKELY(str.len == 0))
   1366         return false;
   1367 
   1368     C4_ASSERT(str.str[0] != '+');
   1369 
   1370     T sign = 1;
   1371     size_t start = 0;
   1372     if(str.str[0] == '-')
   1373     {
   1374         if(C4_UNLIKELY(str.len == ++start))
   1375             return false;
   1376         sign = -1;
   1377     }
   1378 
   1379     bool parsed_ok = true;
   1380     if(str.str[start] != '0') // this should be the common case, so put it first
   1381     {
   1382         parsed_ok = read_dec(str.sub(start), v);
   1383     }
   1384     else if(str.len > start + 1)
   1385     {
   1386         // starts with 0: is it 0x, 0o, 0b?
   1387         const char pfx = str.str[start + 1];
   1388         if(pfx == 'x' || pfx == 'X')
   1389             parsed_ok = str.len > start + 2 && read_hex(str.sub(start + 2), v);
   1390         else if(pfx == 'b' || pfx == 'B')
   1391             parsed_ok = str.len > start + 2 && read_bin(str.sub(start + 2), v);
   1392         else if(pfx == 'o' || pfx == 'O')
   1393             parsed_ok = str.len > start + 2 && read_oct(str.sub(start + 2), v);
   1394         else
   1395             parsed_ok = read_dec(str.sub(start + 1), v);
   1396     }
   1397     else
   1398     {
   1399         parsed_ok = read_dec(str.sub(start), v);
   1400     }
   1401     if(C4_LIKELY(parsed_ok))
   1402         *v *= sign;
   1403     return parsed_ok;
   1404 }
   1405 
   1406 
   1407 /** Select the next range of characters in the string that can be parsed
   1408  * as a signed integral value, and convert it using atoi(). Leading
   1409  * whitespace (space, newline, tabs) is skipped.
   1410  * @return the number of characters read for conversion, or csubstr::npos if the conversion failed
   1411  * @see atoi() if the string is already trimmed to the value to read.
   1412  * @see csubstr::first_int_span() */
   1413 template<class T>
   1414 C4_ALWAYS_INLINE size_t atoi_first(csubstr str, T * C4_RESTRICT v)
   1415 {
   1416     csubstr trimmed = str.first_int_span();
   1417     if(trimmed.len == 0)
   1418         return csubstr::npos;
   1419     if(atoi(trimmed, v))
   1420         return static_cast<size_t>(trimmed.end() - str.begin());
   1421     return csubstr::npos;
   1422 }
   1423 
   1424 
   1425 //-----------------------------------------------------------------------------
   1426 
   1427 /** Convert a trimmed string to an unsigned integral value. The string can be
   1428  * formatted as decimal, binary (prefix 0b or 0B), octal (prefix 0o or 0O)
   1429  * or hexadecimal (prefix 0x or 0X). Every character in the input string is read
   1430  * for the conversion; it must not contain any leading or trailing whitespace.
   1431  *
   1432  * @return true if the conversion was successful.
   1433  *
   1434  * @note overflow is not detected: the return status is true even if
   1435  * the conversion would return a value outside of the type's range, in
   1436  * which case the result will wrap around the type's range.
   1437  *
   1438  * @note If the string has a minus character, the return status
   1439  * will be false.
   1440  *
   1441  * @see atou_first() if the string is not trimmed to the value to read. */
   1442 template<class T>
   1443 bool atou(csubstr str, T * C4_RESTRICT v) noexcept
   1444 {
   1445     C4_STATIC_ASSERT(std::is_integral<T>::value);
   1446 
   1447     if(C4_UNLIKELY(str.len == 0 || str.front() == '-'))
   1448         return false;
   1449 
   1450     bool parsed_ok = true;
   1451     if(str.str[0] != '0')
   1452     {
   1453         parsed_ok = read_dec(str, v);
   1454     }
   1455     else
   1456     {
   1457         if(str.len > 1)
   1458         {
   1459             const char pfx = str.str[1];
   1460             if(pfx == 'x' || pfx == 'X')
   1461                 parsed_ok = str.len > 2 && read_hex(str.sub(2), v);
   1462             else if(pfx == 'b' || pfx == 'B')
   1463                 parsed_ok = str.len > 2 && read_bin(str.sub(2), v);
   1464             else if(pfx == 'o' || pfx == 'O')
   1465                 parsed_ok = str.len > 2 && read_oct(str.sub(2), v);
   1466             else
   1467                 parsed_ok = read_dec(str, v);
   1468         }
   1469         else
   1470         {
   1471             *v = 0; // we know the first character is 0
   1472         }
   1473     }
   1474     return parsed_ok;
   1475 }
   1476 
   1477 
   1478 /** Select the next range of characters in the string that can be parsed
   1479  * as an unsigned integral value, and convert it using atou(). Leading
   1480  * whitespace (space, newline, tabs) is skipped.
   1481  * @return the number of characters read for conversion, or csubstr::npos if the conversion faileds
   1482  * @see atou() if the string is already trimmed to the value to read.
   1483  * @see csubstr::first_uint_span() */
   1484 template<class T>
   1485 C4_ALWAYS_INLINE size_t atou_first(csubstr str, T *v)
   1486 {
   1487     csubstr trimmed = str.first_uint_span();
   1488     if(trimmed.len == 0)
   1489         return csubstr::npos;
   1490     if(atou(trimmed, v))
   1491         return static_cast<size_t>(trimmed.end() - str.begin());
   1492     return csubstr::npos;
   1493 }
   1494 
   1495 
   1496 #ifdef _MSC_VER
   1497 #   pragma warning(pop)
   1498 #elif defined(__clang__)
   1499 #   pragma clang diagnostic pop
   1500 #elif defined(__GNUC__)
   1501 #   pragma GCC diagnostic pop
   1502 #endif
   1503 
   1504 
   1505 //-----------------------------------------------------------------------------
   1506 //-----------------------------------------------------------------------------
   1507 //-----------------------------------------------------------------------------
   1508 namespace detail {
   1509 inline bool check_overflow(csubstr str, csubstr limit) noexcept
   1510 {
   1511     if(str.len == limit.len)
   1512     {
   1513         for(size_t i = 0; i < limit.len; ++i)
   1514         {
   1515             if(str[i] < limit[i])
   1516                 return false;
   1517             else if(str[i] > limit[i])
   1518                 return true;
   1519         }
   1520         return false;
   1521     }
   1522     else
   1523         return str.len > limit.len;
   1524 }
   1525 } // namespace detail
   1526 
   1527 
   1528 /** Test if the following string would overflow when converted to associated
   1529  * types.
   1530  * @return true if number will overflow, false if it fits (or doesn't parse)
   1531  */
   1532 template<class T>
   1533 auto overflows(csubstr str) noexcept
   1534     -> typename std::enable_if<std::is_unsigned<T>::value, bool>::type 
   1535 {
   1536     C4_STATIC_ASSERT(std::is_integral<T>::value);
   1537 
   1538     if(C4_UNLIKELY(str.len == 0))
   1539     {
   1540         return false;
   1541     }
   1542     else if(str.str[0] == '0')
   1543     {
   1544         if (str.len == 1)
   1545             return false;
   1546         switch (str.str[1])
   1547         {
   1548             case 'x':
   1549             case 'X':
   1550             {
   1551                 size_t fno = str.first_not_of('0', 2);
   1552                 if (fno == csubstr::npos)
   1553                     return false;
   1554                 return !(str.len <= fno + (sizeof(T) * 2));
   1555             }
   1556             case 'b':
   1557             case 'B':
   1558             {
   1559                 size_t fno = str.first_not_of('0', 2);
   1560                 if (fno == csubstr::npos)
   1561                     return false;
   1562                 return !(str.len <= fno +(sizeof(T) * 8));
   1563             }
   1564             case 'o':
   1565             case 'O':
   1566             {
   1567                 size_t fno = str.first_not_of('0', 2);
   1568                 if(fno == csubstr::npos)
   1569                     return false;
   1570                 return detail::charconv_digits<T>::is_oct_overflow(str.sub(fno));
   1571             }
   1572             default:
   1573             {
   1574                 size_t fno = str.first_not_of('0', 1);
   1575                 if(fno == csubstr::npos)
   1576                     return false;
   1577                 return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::max_value_dec());
   1578             }
   1579         }
   1580     }
   1581     else if(C4_UNLIKELY(str[0] == '-'))
   1582     {
   1583         return true;
   1584     }
   1585     else
   1586     {
   1587         return detail::check_overflow(str, detail::charconv_digits<T>::max_value_dec());
   1588     }
   1589 }
   1590 
   1591 
   1592 /** Test if the following string would overflow when converted to associated
   1593  * types.
   1594  * @return true if number will overflow, false if it fits (or doesn't parse)
   1595  */
   1596 template<class T>
   1597 auto overflows(csubstr str)
   1598     -> typename std::enable_if<std::is_signed<T>::value, bool>::type 
   1599 {
   1600     C4_STATIC_ASSERT(std::is_integral<T>::value);
   1601     if(C4_UNLIKELY(str.len == 0))
   1602         return false;
   1603     if(str.str[0] == '-')
   1604     {
   1605         if(str.str[1] == '0')
   1606         {
   1607             if(str.len == 2)
   1608                 return false;
   1609             switch(str.str[2])
   1610             {
   1611                 case 'x':
   1612                 case 'X':
   1613                 {
   1614                     size_t fno = str.first_not_of('0', 3);
   1615                     if (fno == csubstr::npos)
   1616                         return false;
   1617                     return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_hex());
   1618                 }
   1619                 case 'b':
   1620                 case 'B':
   1621                 {
   1622                     size_t fno = str.first_not_of('0', 3);
   1623                     if (fno == csubstr::npos)
   1624                         return false;
   1625                     return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_bin());
   1626                 }
   1627                 case 'o':
   1628                 case 'O':
   1629                 {
   1630                     size_t fno = str.first_not_of('0', 3);
   1631                     if(fno == csubstr::npos)
   1632                         return false;
   1633                     return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_oct());
   1634                 }
   1635                 default:
   1636                 {
   1637                     size_t fno = str.first_not_of('0', 2);
   1638                     if(fno == csubstr::npos)
   1639                         return false;
   1640                     return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_dec());
   1641                 }
   1642             }
   1643         }
   1644         else
   1645             return detail::check_overflow(str.sub(1), detail::charconv_digits<T>::min_value_dec());
   1646     }
   1647     else if(str.str[0] == '0')
   1648     {
   1649         if (str.len == 1)
   1650             return false;
   1651         switch(str.str[1])
   1652         {
   1653             case 'x':
   1654             case 'X':
   1655             {
   1656                 size_t fno = str.first_not_of('0', 2);
   1657                 if (fno == csubstr::npos)
   1658                     return false;
   1659                 const size_t len = str.len - fno;
   1660                 return !((len < sizeof (T) * 2) || (len == sizeof(T) * 2 && str[fno] <= '7'));
   1661             }
   1662             case 'b':
   1663             case 'B':
   1664             {
   1665                 size_t fno = str.first_not_of('0', 2);
   1666                 if (fno == csubstr::npos)
   1667                     return false;
   1668                 return !(str.len <= fno + (sizeof(T) * 8 - 1));
   1669             }
   1670             case 'o':
   1671             case 'O':
   1672             {
   1673                 size_t fno = str.first_not_of('0', 2);
   1674                 if(fno == csubstr::npos)
   1675                     return false;
   1676                 return detail::charconv_digits<T>::is_oct_overflow(str.sub(fno));
   1677             }
   1678             default:
   1679             {
   1680                 size_t fno = str.first_not_of('0', 1);
   1681                 if(fno == csubstr::npos)
   1682                     return false;
   1683                 return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::max_value_dec());
   1684             }
   1685         }
   1686     }
   1687     else
   1688         return detail::check_overflow(str, detail::charconv_digits<T>::max_value_dec());
   1689 }
   1690 
   1691 
   1692 //-----------------------------------------------------------------------------
   1693 //-----------------------------------------------------------------------------
   1694 //-----------------------------------------------------------------------------
   1695 
   1696 namespace detail {
   1697 
   1698 
   1699 #if (!C4CORE_HAVE_STD_FROMCHARS)
   1700 /** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */
   1701 template<size_t N>
   1702 void get_real_format_str(char (& C4_RESTRICT fmt)[N], int precision, RealFormat_e formatting, const char* length_modifier="")
   1703 {
   1704     int iret;
   1705     if(precision == -1)
   1706         iret = snprintf(fmt, sizeof(fmt), "%%%s%c", length_modifier, formatting);
   1707     else if(precision == 0)
   1708         iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, formatting);
   1709     else
   1710         iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, formatting);
   1711     C4_ASSERT(iret >= 2 && size_t(iret) < sizeof(fmt));
   1712     C4_UNUSED(iret);
   1713 }
   1714 
   1715 
   1716 /** @todo we're depending on snprintf()/sscanf() for converting to/from
   1717  * floating point numbers. Apparently, this increases the binary size
   1718  * by a considerable amount. There are some lightweight printf
   1719  * implementations:
   1720  *
   1721  * @see http://www.sparetimelabs.com/tinyprintf/tinyprintf.php (BSD)
   1722  * @see https://github.com/weiss/c99-snprintf
   1723  * @see https://github.com/nothings/stb/blob/master/stb_sprintf.h
   1724  * @see http://www.exploringbinary.com/
   1725  * @see https://blog.benoitblanchon.fr/lightweight-float-to-string/
   1726  * @see http://www.ryanjuckett.com/programming/printing-floating-point-numbers/
   1727  */
   1728 template<class T>
   1729 size_t print_one(substr str, const char* full_fmt, T v)
   1730 {
   1731 #ifdef _MSC_VER
   1732     /** use _snprintf() to prevent early termination of the output
   1733      * for writing the null character at the last position
   1734      * @see https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx */
   1735     int iret = _snprintf(str.str, str.len, full_fmt, v);
   1736     if(iret < 0)
   1737     {
   1738         /* when buf.len is not enough, VS returns a negative value.
   1739          * so call it again with a negative value for getting an
   1740          * actual length of the string */
   1741         iret = snprintf(nullptr, 0, full_fmt, v);
   1742         C4_ASSERT(iret > 0);
   1743     }
   1744     size_t ret = (size_t) iret;
   1745     return ret;
   1746 #else
   1747     int iret = snprintf(str.str, str.len, full_fmt, v);
   1748     C4_ASSERT(iret >= 0);
   1749     size_t ret = (size_t) iret;
   1750     if(ret >= str.len)
   1751         ++ret; /* snprintf() reserves the last character to write \0 */
   1752     return ret;
   1753 #endif
   1754 }
   1755 #endif // (!C4CORE_HAVE_STD_FROMCHARS)
   1756 
   1757 
   1758 #if (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT)
   1759 /** scans a string using the given type format, while at the same time
   1760  * allowing non-null-terminated strings AND guaranteeing that the given
   1761  * string length is strictly respected, so that no buffer overflows
   1762  * might occur. */
   1763 template<typename T>
   1764 inline size_t scan_one(csubstr str, const char *type_fmt, T *v)
   1765 {
   1766     /* snscanf() is absolutely needed here as we must be sure that
   1767      * str.len is strictly respected, because substr is
   1768      * generally not null-terminated.
   1769      *
   1770      * Alas, there is no snscanf().
   1771      *
   1772      * So we fake it by using a dynamic format with an explicit
   1773      * field size set to the length of the given span.
   1774      * This trick is taken from:
   1775      * https://stackoverflow.com/a/18368910/5875572 */
   1776 
   1777     /* this is the actual format we'll use for scanning */
   1778     char fmt[16];
   1779 
   1780     /* write the length into it. Eg "%12f".
   1781      * Also, get the number of characters read from the string.
   1782      * So the final format ends up as "%12f%n"*/
   1783     int iret = std::snprintf(fmt, sizeof(fmt), "%%" "%zu" "%s" "%%n", str.len, type_fmt);
   1784     /* no nasty surprises, please! */
   1785     C4_ASSERT(iret >= 0 && size_t(iret) < C4_COUNTOF(fmt));
   1786 
   1787     /* now we scan with confidence that the span length is respected */
   1788     int num_chars;
   1789     iret = std::sscanf(str.str, fmt, v, &num_chars);
   1790     /* scanf returns the number of successful conversions */
   1791     if(iret != 1) return csubstr::npos;
   1792     C4_ASSERT(num_chars >= 0);
   1793     return (size_t)(num_chars);
   1794 }
   1795 #endif // (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT)
   1796 
   1797 
   1798 #if C4CORE_HAVE_STD_TOCHARS
   1799 template<class T>
   1800 C4_ALWAYS_INLINE size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
   1801 {
   1802     std::to_chars_result result;
   1803     size_t pos = 0;
   1804     if(formatting == FTOA_HEXA)
   1805     {
   1806         if(buf.len > size_t(2))
   1807         {
   1808             buf.str[0] = '0';
   1809             buf.str[1] = 'x';
   1810         }
   1811         pos += size_t(2);
   1812     }
   1813     if(precision == -1)
   1814         result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting);
   1815     else
   1816         result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting, precision);
   1817     if(result.ec == std::errc())
   1818     {
   1819         // all good, no errors.
   1820         C4_ASSERT(result.ptr >= buf.str);
   1821         ptrdiff_t delta = result.ptr - buf.str;
   1822         return static_cast<size_t>(delta);
   1823     }
   1824     C4_ASSERT(result.ec == std::errc::value_too_large);
   1825     // This is unfortunate.
   1826     //
   1827     // When the result can't fit in the given buffer,
   1828     // std::to_chars() returns the end pointer it was originally
   1829     // given, which is useless because here we would like to know
   1830     // _exactly_ how many characters the buffer must have to fit
   1831     // the result.
   1832     //
   1833     // So we take the pessimistic view, and assume as many digits
   1834     // as could ever be required:
   1835     size_t ret = static_cast<size_t>(std::numeric_limits<T>::max_digits10);
   1836     return ret > buf.len ? ret : buf.len + 1;
   1837 }
   1838 #endif // C4CORE_HAVE_STD_TOCHARS
   1839 
   1840 
   1841 #if C4CORE_HAVE_FAST_FLOAT
   1842 template<class T>
   1843 C4_ALWAYS_INLINE bool scan_rhex(csubstr s, T *C4_RESTRICT val) noexcept
   1844 {
   1845     C4_ASSERT(s.len > 0);
   1846     C4_ASSERT(s.str[0] != '-');
   1847     C4_ASSERT(s.str[0] != '+');
   1848     C4_ASSERT(!s.begins_with("0x"));
   1849     C4_ASSERT(!s.begins_with("0X"));
   1850     size_t pos = 0;
   1851     // integer part
   1852     for( ; pos < s.len; ++pos)
   1853     {
   1854         const char c = s.str[pos];
   1855         if(c >= '0' && c <= '9')
   1856             *val = *val * T(16) + T(c - '0');
   1857         else if(c >= 'a' && c <= 'f')
   1858             *val = *val * T(16) + T(c - 'a');
   1859         else if(c >= 'A' && c <= 'F')
   1860             *val = *val * T(16) + T(c - 'A');
   1861         else if(c == '.')
   1862         {
   1863             ++pos;
   1864             break; // follow on to mantissa
   1865         }
   1866         else if(c == 'p' || c == 'P')
   1867         {
   1868             ++pos;
   1869             goto power; // no mantissa given, jump to power
   1870         }
   1871         else
   1872         {
   1873             return false;
   1874         }
   1875     }
   1876     // mantissa
   1877     {
   1878         // 0.0625 == 1/16 == value of first digit after the comma
   1879         for(T digit = T(0.0625); pos < s.len; ++pos, digit /= T(16))
   1880         {
   1881             const char c = s.str[pos];
   1882             if(c >= '0' && c <= '9')
   1883                 *val += digit * T(c - '0');
   1884             else if(c >= 'a' && c <= 'f')
   1885                 *val += digit * T(c - 'a');
   1886             else if(c >= 'A' && c <= 'F')
   1887                 *val += digit * T(c - 'A');
   1888             else if(c == 'p' || c == 'P')
   1889             {
   1890                 ++pos;
   1891                 goto power; // mantissa finished, jump to power
   1892             }
   1893             else
   1894             {
   1895                 return false;
   1896             }
   1897         }
   1898     }
   1899     return true;
   1900 power:
   1901     if(C4_LIKELY(pos < s.len))
   1902     {
   1903         if(s.str[pos] == '+') // atoi() cannot handle a leading '+'
   1904             ++pos;
   1905         if(C4_LIKELY(pos < s.len))
   1906         {
   1907             int16_t powval = {};
   1908             if(C4_LIKELY(atoi(s.sub(pos), &powval)))
   1909             {
   1910                 *val *= ipow<T, int16_t, 16>(powval);
   1911                 return true;
   1912             }
   1913         }
   1914     }
   1915     return false;
   1916 }
   1917 #endif
   1918 
   1919 } // namespace detail
   1920 
   1921 
   1922 #undef _c4appendhex
   1923 #undef _c4append
   1924 
   1925 
   1926 /** Convert a single-precision real number to string.  The string will
   1927  * in general be NOT null-terminated.  For FTOA_FLEX, \p precision is
   1928  * the number of significand digits. Otherwise \p precision is the
   1929  * number of decimals. It is safe to call this function with an empty
   1930  * or too-small buffer.
   1931  *
   1932  * @return the size of the buffer needed to write the number
   1933  */
   1934 C4_ALWAYS_INLINE size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
   1935 {
   1936 #if C4CORE_HAVE_STD_TOCHARS
   1937     return detail::rtoa(str, v, precision, formatting);
   1938 #else
   1939     char fmt[16];
   1940     detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"");
   1941     return detail::print_one(str, fmt, v);
   1942 #endif
   1943 }
   1944 
   1945 
   1946 /** Convert a double-precision real number to string.  The string will
   1947  * in general be NOT null-terminated.  For FTOA_FLEX, \p precision is
   1948  * the number of significand digits. Otherwise \p precision is the
   1949  * number of decimals. It is safe to call this function with an empty
   1950  * or too-small buffer.
   1951  *
   1952  * @return the size of the buffer needed to write the number
   1953  */
   1954 C4_ALWAYS_INLINE size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
   1955 {
   1956 #if C4CORE_HAVE_STD_TOCHARS
   1957     return detail::rtoa(str, v, precision, formatting);
   1958 #else
   1959     char fmt[16];
   1960     detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"l");
   1961     return detail::print_one(str, fmt, v);
   1962 #endif
   1963 }
   1964 
   1965 
   1966 /** Convert a string to a single precision real number.
   1967  * The input string must be trimmed to the value, ie
   1968  * no leading or trailing whitespace can be present.
   1969  * @return true iff the conversion succeeded
   1970  * @see atof_first() if the string is not trimmed
   1971  */
   1972 C4_ALWAYS_INLINE bool atof(csubstr str, float * C4_RESTRICT v) noexcept
   1973 {
   1974     C4_ASSERT(str.len > 0);
   1975     C4_ASSERT(str.triml(" \r\t\n").len == str.len);
   1976 #if C4CORE_HAVE_FAST_FLOAT
   1977     // fastfloat cannot parse hexadecimal floats
   1978     bool isneg = (str.str[0] == '-');
   1979     csubstr rem = str.sub(isneg || str.str[0] == '+');
   1980     if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))
   1981     {
   1982         fast_float::from_chars_result result;
   1983         result = fast_float::from_chars(str.str, str.str + str.len, *v);
   1984         return result.ec == std::errc();
   1985     }
   1986     else if(detail::scan_rhex(rem.sub(2), v))
   1987     {
   1988         *v *= isneg ? -1.f : 1.f;
   1989         return true;
   1990     }
   1991     return false;
   1992 #elif C4CORE_HAVE_STD_FROMCHARS
   1993     std::from_chars_result result;
   1994     result = std::from_chars(str.str, str.str + str.len, *v);
   1995     return result.ec == std::errc();
   1996 #else
   1997     csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+');
   1998     if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))
   1999         return detail::scan_one(str, "f", v) != csubstr::npos;
   2000     else
   2001         return detail::scan_one(str, "a", v) != csubstr::npos;
   2002 #endif
   2003 }
   2004 
   2005 
   2006 /** Convert a string to a double precision real number.
   2007  * The input string must be trimmed to the value, ie
   2008  * no leading or trailing whitespace can be present.
   2009  * @return true iff the conversion succeeded
   2010  * @see atod_first() if the string is not trimmed
   2011  */
   2012 C4_ALWAYS_INLINE bool atod(csubstr str, double * C4_RESTRICT v) noexcept
   2013 {
   2014     C4_ASSERT(str.triml(" \r\t\n").len == str.len);
   2015 #if C4CORE_HAVE_FAST_FLOAT
   2016     // fastfloat cannot parse hexadecimal floats
   2017     bool isneg = (str.str[0] == '-');
   2018     csubstr rem = str.sub(isneg || str.str[0] == '+');
   2019     if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))
   2020     {
   2021         fast_float::from_chars_result result;
   2022         result = fast_float::from_chars(str.str, str.str + str.len, *v);
   2023         return result.ec == std::errc();
   2024     }
   2025     else if(detail::scan_rhex(rem.sub(2), v))
   2026     {
   2027         *v *= isneg ? -1. : 1.;
   2028         return true;
   2029     }
   2030     return false;
   2031 #elif C4CORE_HAVE_STD_FROMCHARS
   2032     std::from_chars_result result;
   2033     result = std::from_chars(str.str, str.str + str.len, *v);
   2034     return result.ec == std::errc();
   2035 #else
   2036     csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+');
   2037     if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))
   2038         return detail::scan_one(str, "lf", v) != csubstr::npos;
   2039     else
   2040         return detail::scan_one(str, "la", v) != csubstr::npos;
   2041 #endif
   2042 }
   2043 
   2044 
   2045 /** Convert a string to a single precision real number.
   2046  * Leading whitespace is skipped until valid characters are found.
   2047  * @return the number of characters read from the string, or npos if
   2048  * conversion was not successful or if the string was empty */
   2049 inline size_t atof_first(csubstr str, float * C4_RESTRICT v) noexcept
   2050 {
   2051     csubstr trimmed = str.first_real_span();
   2052     if(trimmed.len == 0)
   2053         return csubstr::npos;
   2054     if(atof(trimmed, v))
   2055         return static_cast<size_t>(trimmed.end() - str.begin());
   2056     return csubstr::npos;
   2057 }
   2058 
   2059 
   2060 /** Convert a string to a double precision real number.
   2061  * Leading whitespace is skipped until valid characters are found.
   2062  * @return the number of characters read from the string, or npos if
   2063  * conversion was not successful or if the string was empty */
   2064 inline size_t atod_first(csubstr str, double * C4_RESTRICT v) noexcept
   2065 {
   2066     csubstr trimmed = str.first_real_span();
   2067     if(trimmed.len == 0)
   2068         return csubstr::npos;
   2069     if(atod(trimmed, v))
   2070         return static_cast<size_t>(trimmed.end() - str.begin());
   2071     return csubstr::npos;
   2072 }
   2073 
   2074 
   2075 //-----------------------------------------------------------------------------
   2076 //-----------------------------------------------------------------------------
   2077 //-----------------------------------------------------------------------------
   2078 // generic versions
   2079 
   2080 C4_ALWAYS_INLINE size_t xtoa(substr s,  uint8_t v) noexcept { return write_dec(s, v); }
   2081 C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) noexcept { return write_dec(s, v); }
   2082 C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) noexcept { return write_dec(s, v); }
   2083 C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) noexcept { return write_dec(s, v); }
   2084 C4_ALWAYS_INLINE size_t xtoa(substr s,   int8_t v) noexcept { return itoa(s, v); }
   2085 C4_ALWAYS_INLINE size_t xtoa(substr s,  int16_t v) noexcept { return itoa(s, v); }
   2086 C4_ALWAYS_INLINE size_t xtoa(substr s,  int32_t v) noexcept { return itoa(s, v); }
   2087 C4_ALWAYS_INLINE size_t xtoa(substr s,  int64_t v) noexcept { return itoa(s, v); }
   2088 C4_ALWAYS_INLINE size_t xtoa(substr s,    float v) noexcept { return ftoa(s, v); }
   2089 C4_ALWAYS_INLINE size_t xtoa(substr s,   double v) noexcept { return dtoa(s, v); }
   2090 
   2091 C4_ALWAYS_INLINE size_t xtoa(substr s,  uint8_t v,  uint8_t radix) noexcept { return utoa(s, v, radix); }
   2092 C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix) noexcept { return utoa(s, v, radix); }
   2093 C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix) noexcept { return utoa(s, v, radix); }
   2094 C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix) noexcept { return utoa(s, v, radix); }
   2095 C4_ALWAYS_INLINE size_t xtoa(substr s,   int8_t v,   int8_t radix) noexcept { return itoa(s, v, radix); }
   2096 C4_ALWAYS_INLINE size_t xtoa(substr s,  int16_t v,  int16_t radix) noexcept { return itoa(s, v, radix); }
   2097 C4_ALWAYS_INLINE size_t xtoa(substr s,  int32_t v,  int32_t radix) noexcept { return itoa(s, v, radix); }
   2098 C4_ALWAYS_INLINE size_t xtoa(substr s,  int64_t v,  int64_t radix) noexcept { return itoa(s, v, radix); }
   2099 
   2100 C4_ALWAYS_INLINE size_t xtoa(substr s,  uint8_t v,  uint8_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); }
   2101 C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); }
   2102 C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); }
   2103 C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); }
   2104 C4_ALWAYS_INLINE size_t xtoa(substr s,   int8_t v,   int8_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); }
   2105 C4_ALWAYS_INLINE size_t xtoa(substr s,  int16_t v,  int16_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); }
   2106 C4_ALWAYS_INLINE size_t xtoa(substr s,  int32_t v,  int32_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); }
   2107 C4_ALWAYS_INLINE size_t xtoa(substr s,  int64_t v,  int64_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); }
   2108 
   2109 C4_ALWAYS_INLINE size_t xtoa(substr s,  float v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return ftoa(s, v, precision, formatting); }
   2110 C4_ALWAYS_INLINE size_t xtoa(substr s, double v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return dtoa(s, v, precision, formatting); }
   2111 
   2112 C4_ALWAYS_INLINE bool atox(csubstr s,  uint8_t *C4_RESTRICT v) noexcept { return atou(s, v); }
   2113 C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) noexcept { return atou(s, v); }
   2114 C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) noexcept { return atou(s, v); }
   2115 C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) noexcept { return atou(s, v); }
   2116 C4_ALWAYS_INLINE bool atox(csubstr s,   int8_t *C4_RESTRICT v) noexcept { return atoi(s, v); }
   2117 C4_ALWAYS_INLINE bool atox(csubstr s,  int16_t *C4_RESTRICT v) noexcept { return atoi(s, v); }
   2118 C4_ALWAYS_INLINE bool atox(csubstr s,  int32_t *C4_RESTRICT v) noexcept { return atoi(s, v); }
   2119 C4_ALWAYS_INLINE bool atox(csubstr s,  int64_t *C4_RESTRICT v) noexcept { return atoi(s, v); }
   2120 C4_ALWAYS_INLINE bool atox(csubstr s,    float *C4_RESTRICT v) noexcept { return atof(s, v); }
   2121 C4_ALWAYS_INLINE bool atox(csubstr s,   double *C4_RESTRICT v) noexcept { return atod(s, v); }
   2122 
   2123 C4_ALWAYS_INLINE size_t to_chars(substr buf,  uint8_t v) noexcept { return write_dec(buf, v); }
   2124 C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) noexcept { return write_dec(buf, v); }
   2125 C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) noexcept { return write_dec(buf, v); }
   2126 C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) noexcept { return write_dec(buf, v); }
   2127 C4_ALWAYS_INLINE size_t to_chars(substr buf,   int8_t v) noexcept { return itoa(buf, v); }
   2128 C4_ALWAYS_INLINE size_t to_chars(substr buf,  int16_t v) noexcept { return itoa(buf, v); }
   2129 C4_ALWAYS_INLINE size_t to_chars(substr buf,  int32_t v) noexcept { return itoa(buf, v); }
   2130 C4_ALWAYS_INLINE size_t to_chars(substr buf,  int64_t v) noexcept { return itoa(buf, v); }
   2131 C4_ALWAYS_INLINE size_t to_chars(substr buf,    float v) noexcept { return ftoa(buf, v); }
   2132 C4_ALWAYS_INLINE size_t to_chars(substr buf,   double v) noexcept { return dtoa(buf, v); }
   2133 
   2134 C4_ALWAYS_INLINE bool from_chars(csubstr buf,  uint8_t *C4_RESTRICT v) noexcept { return atou(buf, v); }
   2135 C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou(buf, v); }
   2136 C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou(buf, v); }
   2137 C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou(buf, v); }
   2138 C4_ALWAYS_INLINE bool from_chars(csubstr buf,   int8_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }
   2139 C4_ALWAYS_INLINE bool from_chars(csubstr buf,  int16_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }
   2140 C4_ALWAYS_INLINE bool from_chars(csubstr buf,  int32_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }
   2141 C4_ALWAYS_INLINE bool from_chars(csubstr buf,  int64_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }
   2142 C4_ALWAYS_INLINE bool from_chars(csubstr buf,    float *C4_RESTRICT v) noexcept { return atof(buf, v); }
   2143 C4_ALWAYS_INLINE bool from_chars(csubstr buf,   double *C4_RESTRICT v) noexcept { return atod(buf, v); }
   2144 
   2145 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,  uint8_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
   2146 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
   2147 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
   2148 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
   2149 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,   int8_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
   2150 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,  int16_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
   2151 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,  int32_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
   2152 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,  int64_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
   2153 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,    float *C4_RESTRICT v) noexcept { return atof_first(buf, v); }
   2154 C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf,   double *C4_RESTRICT v) noexcept { return atod_first(buf, v); }
   2155 
   2156 
   2157 //-----------------------------------------------------------------------------
   2158 // on some platforms, (unsigned) int and (unsigned) long
   2159 // are not any of the fixed length types above
   2160 
   2161 #define _C4_IF_NOT_FIXED_LENGTH_I(T, ty) C4_ALWAYS_INLINE typename std::enable_if<std::  is_signed<T>::value && !is_fixed_length<T>::value_i, ty>
   2162 #define _C4_IF_NOT_FIXED_LENGTH_U(T, ty) C4_ALWAYS_INLINE typename std::enable_if<std::is_unsigned<T>::value && !is_fixed_length<T>::value_u, ty>
   2163 
   2164 template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type xtoa(substr buf, T v) noexcept { return itoa(buf, v); }
   2165 template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type xtoa(substr buf, T v) noexcept { return write_dec(buf, v); }
   2166 
   2167 template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool  )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); }
   2168 template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool  )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); }
   2169 
   2170 template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type to_chars(substr buf, T v) noexcept { return itoa(buf, v); }
   2171 template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type to_chars(substr buf, T v) noexcept { return write_dec(buf, v); }
   2172 
   2173 template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool  )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); }
   2174 template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool  )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); }
   2175 
   2176 template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
   2177 template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
   2178 
   2179 #undef _C4_IF_NOT_FIXED_LENGTH_I
   2180 #undef _C4_IF_NOT_FIXED_LENGTH_U
   2181 
   2182 
   2183 //-----------------------------------------------------------------------------
   2184 // for pointers
   2185 
   2186 template <class T> C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); }
   2187 template <class T> C4_ALWAYS_INLINE bool   atox(csubstr s, T **v) noexcept { intptr_t tmp; bool ret = atox(s, &tmp); if(ret) { *v = (T*)tmp; } return ret; }
   2188 template <class T> C4_ALWAYS_INLINE size_t to_chars(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); }
   2189 template <class T> C4_ALWAYS_INLINE bool   from_chars(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; }
   2190 template <class T> C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars_first(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; }
   2191 
   2192 
   2193 //-----------------------------------------------------------------------------
   2194 //-----------------------------------------------------------------------------
   2195 //-----------------------------------------------------------------------------
   2196 /** call to_chars() and return a substr consisting of the
   2197  * written portion of the input buffer. Ie, same as to_chars(),
   2198  * but return a substr instead of a size_t.
   2199  *
   2200  * @see to_chars() */
   2201 template<class T>
   2202 C4_ALWAYS_INLINE substr to_chars_sub(substr buf, T const& C4_RESTRICT v) noexcept
   2203 {
   2204     size_t sz = to_chars(buf, v);
   2205     return buf.left_of(sz <= buf.len ? sz : buf.len);
   2206 }
   2207 
   2208 //-----------------------------------------------------------------------------
   2209 //-----------------------------------------------------------------------------
   2210 //-----------------------------------------------------------------------------
   2211 // bool implementation
   2212 
   2213 C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept
   2214 {
   2215     int val = v;
   2216     return to_chars(buf, val);
   2217 }
   2218 
   2219 inline bool from_chars(csubstr buf, bool * C4_RESTRICT v) noexcept
   2220 {
   2221     if(buf == '0')
   2222     {
   2223         *v = false; return true;
   2224     }
   2225     else if(buf == '1')
   2226     {
   2227         *v = true; return true;
   2228     }
   2229     else if(buf == "false")
   2230     {
   2231         *v = false; return true;
   2232     }
   2233     else if(buf == "true")
   2234     {
   2235         *v = true; return true;
   2236     }
   2237     else if(buf == "False")
   2238     {
   2239         *v = false; return true;
   2240     }
   2241     else if(buf == "True")
   2242     {
   2243         *v = true; return true;
   2244     }
   2245     else if(buf == "FALSE")
   2246     {
   2247         *v = false; return true;
   2248     }
   2249     else if(buf == "TRUE")
   2250     {
   2251         *v = true; return true;
   2252     }
   2253     // fallback to c-style int bools
   2254     int val = 0;
   2255     bool ret = from_chars(buf, &val);
   2256     if(C4_LIKELY(ret))
   2257     {
   2258         *v = (val != 0);
   2259     }
   2260     return ret;
   2261 }
   2262 
   2263 inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v) noexcept
   2264 {
   2265     csubstr trimmed = buf.first_non_empty_span();
   2266     if(trimmed.len == 0 || !from_chars(buf, v))
   2267         return csubstr::npos;
   2268     return trimmed.len;
   2269 }
   2270 
   2271 
   2272 //-----------------------------------------------------------------------------
   2273 // single-char implementation
   2274 
   2275 inline size_t to_chars(substr buf, char v) noexcept
   2276 {
   2277     if(buf.len > 0)
   2278     {
   2279         C4_XASSERT(buf.str);
   2280         buf.str[0] = v;
   2281     }
   2282     return 1;
   2283 }
   2284 
   2285 /** extract a single character from a substring
   2286  * @note to extract a string instead and not just a single character, use the csubstr overload */
   2287 inline bool from_chars(csubstr buf, char * C4_RESTRICT v) noexcept
   2288 {
   2289     if(buf.len != 1)
   2290         return false;
   2291     C4_XASSERT(buf.str);
   2292     *v = buf.str[0];
   2293     return true;
   2294 }
   2295 
   2296 inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v) noexcept
   2297 {
   2298     if(buf.len < 1)
   2299         return csubstr::npos;
   2300     *v = buf.str[0];
   2301     return 1;
   2302 }
   2303 
   2304 
   2305 //-----------------------------------------------------------------------------
   2306 // csubstr implementation
   2307 
   2308 inline size_t to_chars(substr buf, csubstr v) noexcept
   2309 {
   2310     C4_ASSERT(!buf.overlaps(v));
   2311     size_t len = buf.len < v.len ? buf.len : v.len;
   2312     // calling memcpy with null strings is undefined behavior
   2313     // and will wreak havoc in calling code's branches.
   2314     // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
   2315     if(len)
   2316     {
   2317         C4_ASSERT(buf.str != nullptr);
   2318         C4_ASSERT(v.str != nullptr);
   2319         memcpy(buf.str, v.str, len);
   2320     }
   2321     return v.len;
   2322 }
   2323 
   2324 inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept
   2325 {
   2326     *v = buf;
   2327     return true;
   2328 }
   2329 
   2330 inline size_t from_chars_first(substr buf, csubstr * C4_RESTRICT v) noexcept
   2331 {
   2332     csubstr trimmed = buf.first_non_empty_span();
   2333     if(trimmed.len == 0)
   2334         return csubstr::npos;
   2335     *v = trimmed;
   2336     return static_cast<size_t>(trimmed.end() - buf.begin());
   2337 }
   2338 
   2339 
   2340 //-----------------------------------------------------------------------------
   2341 // substr
   2342 
   2343 inline size_t to_chars(substr buf, substr v) noexcept
   2344 {
   2345     C4_ASSERT(!buf.overlaps(v));
   2346     size_t len = buf.len < v.len ? buf.len : v.len;
   2347     // calling memcpy with null strings is undefined behavior
   2348     // and will wreak havoc in calling code's branches.
   2349     // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
   2350     if(len)
   2351     {
   2352         C4_ASSERT(buf.str != nullptr);
   2353         C4_ASSERT(v.str != nullptr);
   2354         memcpy(buf.str, v.str, len);
   2355     }
   2356     return v.len;
   2357 }
   2358 
   2359 inline bool from_chars(csubstr buf, substr * C4_RESTRICT v) noexcept
   2360 {
   2361     C4_ASSERT(!buf.overlaps(*v));
   2362     // is the destination buffer wide enough?
   2363     if(v->len >= buf.len)
   2364     {
   2365         // calling memcpy with null strings is undefined behavior
   2366         // and will wreak havoc in calling code's branches.
   2367         // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
   2368         if(buf.len)
   2369         {
   2370             C4_ASSERT(buf.str != nullptr);
   2371             C4_ASSERT(v->str != nullptr);
   2372             memcpy(v->str, buf.str, buf.len);
   2373         }
   2374         v->len = buf.len;
   2375         return true;
   2376     }
   2377     return false;
   2378 }
   2379 
   2380 inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v) noexcept
   2381 {
   2382     csubstr trimmed = buf.first_non_empty_span();
   2383     C4_ASSERT(!trimmed.overlaps(*v));
   2384     if(C4_UNLIKELY(trimmed.len == 0))
   2385         return csubstr::npos;
   2386     size_t len = trimmed.len > v->len ? v->len : trimmed.len;
   2387     // calling memcpy with null strings is undefined behavior
   2388     // and will wreak havoc in calling code's branches.
   2389     // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
   2390     if(len)
   2391     {
   2392         C4_ASSERT(buf.str != nullptr);
   2393         C4_ASSERT(v->str != nullptr);
   2394         memcpy(v->str, trimmed.str, len);
   2395     }
   2396     if(C4_UNLIKELY(trimmed.len > v->len))
   2397         return csubstr::npos;
   2398     return static_cast<size_t>(trimmed.end() - buf.begin());
   2399 }
   2400 
   2401 
   2402 //-----------------------------------------------------------------------------
   2403 
   2404 template<size_t N>
   2405 inline size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N]) noexcept
   2406 {
   2407     csubstr sp(v);
   2408     return to_chars(buf, sp);
   2409 }
   2410 
   2411 inline size_t to_chars(substr buf, const char * C4_RESTRICT v) noexcept
   2412 {
   2413     return to_chars(buf, to_csubstr(v));
   2414 }
   2415 
   2416 } // namespace c4
   2417 
   2418 #ifdef _MSC_VER
   2419 #   pragma warning(pop)
   2420 #endif
   2421 
   2422 #if defined(__clang__)
   2423 #   pragma clang diagnostic pop
   2424 #elif defined(__GNUC__)
   2425 #   pragma GCC diagnostic pop
   2426 #endif
   2427 
   2428 #endif /* _C4_CHARCONV_HPP_ */