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