rapidyaml  0.16.0
parseandemitYAML,anddoitfast
format.hpp
Go to the documentation of this file.
1 #ifndef C4_FORMAT_HPP_
2 #define C4_FORMAT_HPP_
3 
4 /** @file format.hpp provides type-safe facilities for formatting arguments
5  * to string buffers */
6 
7 #include "c4/charconv.hpp"
8 #include "c4/blob.hpp"
9 
10 
11 #if defined(_MSC_VER) && !defined(__clang__)
12 # pragma warning(push)
13 # if C4_MSVC_VERSION != C4_MSVC_VERSION_2017
14 # pragma warning(disable: 4800) // forcing value to bool 'true' or 'false' (performance warning)
15 # endif
16 # pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe
17 #elif defined(__clang__)
18 # pragma clang diagnostic push
19 #elif defined(__GNUC__)
20 # pragma GCC diagnostic push
21 # pragma GCC diagnostic ignored "-Wuseless-cast"
22 #endif
23 // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast,*avoid-goto*)
24 
25 /** @defgroup doc_format_utils Format utilities
26  *
27  * @brief Provides generic and type-safe formatting/scanning utilities
28  * built on top of @ref doc_to_chars() and @ref doc_from_chars,
29  * forwarding the arguments to these functions, which in turn use the
30  * @ref doc_charconv utilities. Like @ref doc_charconv, the formatting
31  * facilities are very efficient and many times faster than printf().
32  *
33  * @see [a formatting sample in rapidyaml's docs](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#gac2425b515eb552589708cfff70c52b14)
34  * */
35 
36 /** @defgroup doc_format_specifiers Format specifiers
37  *
38  * @brief Format specifiers are tag types and functions that are used
39  * together with @ref doc_to_chars and @ref doc_from_chars
40  *
41  * @see [a formatting sample in rapidyaml's docs](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#gac2425b515eb552589708cfff70c52b14)
42  * @ingroup doc_format_utils */
43 
44 namespace c4 {
45 
46 /** @addtogroup doc_format_utils
47  * @{ */
48 
49 //-----------------------------------------------------------------------------
50 //-----------------------------------------------------------------------------
51 //-----------------------------------------------------------------------------
52 // formatting truthy types as booleans
53 
54 namespace fmt {
55 
56 /** @addtogroup doc_format_specifiers
57  * @{ */
58 
59 /** @defgroup doc_boolean_specifiers boolean specifiers
60  * @{ */
61 
62 struct boolalpha_
63 {
64  bool val;
65 };
66 
67 /** tag function to mark a variable to be written as an alphabetic
68  * boolean, ie as either true or false */
69 template<class T>
70 boolalpha_ boolalpha(T const& val=false)
71 {
72  return boolalpha_{val ? true : false};
73 }
74 
75 /** @} */
76 
77 /** @} */
78 
79 } // namespace fmt
80 
81 /** write a variable as an alphabetic boolean, ie as either true or
82  * false
83  * @ingroup doc_to_chars */
84 inline size_t to_chars(substr buf, fmt::boolalpha_ fmt)
85 {
86  return to_chars(buf, fmt.val ? "true" : "false");
87 }
88 
89 
90 
91 //-----------------------------------------------------------------------------
92 //-----------------------------------------------------------------------------
93 //-----------------------------------------------------------------------------
94 // formatting integral types
95 
96 namespace fmt {
97 
98 /** @addtogroup doc_format_specifiers
99  * @{ */
100 
101 /** @defgroup doc_integer_specifiers Integer specifiers
102  * @{ */
103 
104 /** format an integral type with a custom radix */
105 template<typename T>
106 struct integral_
107 {
108  C4_STATIC_ASSERT(std::is_integral<T>::value);
109  T val;
110  T radix;
111  C4_ALWAYS_INLINE integral_(T val_, T radix_) : val(val_), radix(radix_) {}
112 };
113 
114 /** format an integral type with a custom radix, and pad with zeroes on the left */
115 template<typename T>
117 {
118  C4_STATIC_ASSERT(std::is_integral<T>::value);
119  T val;
120  T radix;
121  size_t num_digits;
122  C4_ALWAYS_INLINE integral_padded_(T val_, T radix_, size_t nd) : val(val_), radix(radix_), num_digits(nd) {}
123 };
124 
125 
126 /** format an integral type with a custom radix */
127 template<class T>
128 C4_ALWAYS_INLINE integral_<T> integral(T val, T radix=10)
129 {
130  return integral_<T>(val, radix);
131 }
132 /** format an integral type with a custom radix */
133 template<class T>
134 C4_ALWAYS_INLINE integral_<intptr_t> integral(T const* val, T radix=10)
135 {
136  return integral_<intptr_t>(reinterpret_cast<intptr_t>(val), static_cast<intptr_t>(radix));
137 }
138 /** format an integral type with a custom radix */
139 template<class T>
140 C4_ALWAYS_INLINE integral_<intptr_t> integral(std::nullptr_t, T radix=10)
141 {
142  return integral_<intptr_t>(intptr_t(0), static_cast<intptr_t>(radix));
143 }
144 
145 
146 /** format the pointer as an hexadecimal value */
147 template<class T>
149 {
150  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16));
151 }
152 /** format the pointer as an hexadecimal value */
153 template<class T>
154 inline integral_<intptr_t> hex(T const* v)
155 {
156  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16));
157 }
158 /** format null as an hexadecimal value
159  * @overload hex */
160 inline integral_<intptr_t> hex(std::nullptr_t)
161 {
162  return integral_<intptr_t>(0, intptr_t(16));
163 }
164 /** format the integral_ argument as an hexadecimal value
165  * @overload hex */
166 template<class T>
167 inline integral_<T> hex(T v)
168 {
169  return integral_<T>(v, T(16));
170 }
171 
172 /** format the pointer as an octal value */
173 template<class T>
174 inline integral_<intptr_t> oct(T const* v)
175 {
176  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
177 }
178 /** format the pointer as an octal value */
179 template<class T>
181 {
182  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
183 }
184 /** format null as an octal value */
185 inline integral_<intptr_t> oct(std::nullptr_t)
186 {
187  return integral_<intptr_t>(intptr_t(0), intptr_t(8));
188 }
189 /** format the integral_ argument as an octal value */
190 template<class T>
191 inline integral_<T> oct(T v)
192 {
193  return integral_<T>(v, T(8));
194 }
195 
196 /** format the pointer as a binary 0-1 value
197  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
198 template<class T>
199 inline integral_<intptr_t> bin(T const* v)
200 {
201  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(2));
202 }
203 /** format the pointer as a binary 0-1 value
204  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
205 template<class T>
207 {
208  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(2));
209 }
210 /** format null as a binary 0-1 value
211  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
212 inline integral_<intptr_t> bin(std::nullptr_t)
213 {
214  return integral_<intptr_t>(intptr_t(0), intptr_t(2));
215 }
216 /** format the integral_ argument as a binary 0-1 value
217  * @see c4::raw() if you want to use a raw memcpy-based binary dump instead of 0-1 formatting */
218 template<class T>
219 inline integral_<T> bin(T v)
220 {
221  return integral_<T>(v, T(2));
222 }
223 
224 /** @} */ // integer_specifiers
225 
226 
227 /** @defgroup doc_zpad Pad the number with zeroes on the left
228  * @{ */
229 
230 /** pad the argument with zeroes on the left, with decimal radix */
231 template<class T>
232 C4_ALWAYS_INLINE integral_padded_<T> zpad(T val, size_t num_digits)
233 {
234  return integral_padded_<T>(val, T(10), num_digits);
235 }
236 /** pad the argument with zeroes on the left */
237 template<class T>
238 C4_ALWAYS_INLINE integral_padded_<T> zpad(integral_<T> val, size_t num_digits)
239 {
240  return integral_padded_<T>(val.val, val.radix, num_digits);
241 }
242 /** pad the argument with zeroes on the left */
243 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(std::nullptr_t, size_t num_digits)
244 {
245  return integral_padded_<intptr_t>(0, 16, num_digits);
246 }
247 /** pad the argument with zeroes on the left */
248 template<class T>
249 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T const* val, size_t num_digits)
250 {
251  return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits);
252 }
253 template<class T>
254 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T * val, size_t num_digits)
255 {
256  return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits);
257 }
258 
259 /** @} */ // zpad
260 
261 
262 /** @defgroup doc_overflow_checked Check read for overflow
263  * @{ */
264 
265 template<class T>
267 {
268  static_assert(std::is_integral<T>::value, "range checking only for integral types");
269  C4_ALWAYS_INLINE overflow_checked_(T &val_) : val(&val_) {}
270  T *val;
271 };
272 template<class T>
273 C4_ALWAYS_INLINE overflow_checked_<T> overflow_checked(T &val)
274 {
275  return overflow_checked_<T>(val);
276 }
277 
278 /** @} */ // overflow_checked
279 
280 /** @} */ // format_specifiers
281 
282 
283 } // namespace fmt
284 
285 /** format an integer signed type
286  * @ingroup doc_to_chars */
287 template<typename T>
288 C4_ALWAYS_INLINE auto to_chars(substr buf, fmt::integral_<T> fmt)
289  -> typename std::enable_if<std::is_signed<T>::value, size_t>::type
290 {
291  return itoa(buf, fmt.val, fmt.radix);
292 }
293 /** format an integer signed type, pad with zeroes
294  * @ingroup doc_to_chars */
295 template<typename T>
296 C4_ALWAYS_INLINE auto to_chars(substr buf, fmt::integral_padded_<T> fmt)
297  -> typename std::enable_if<std::is_signed<T>::value, size_t>::type
298 {
299  return itoa(buf, fmt.val, fmt.radix, fmt.num_digits);
300 }
301 
302 /** format an integer unsigned type
303  * @ingroup doc_to_chars */
304 template<typename T>
305 C4_ALWAYS_INLINE auto to_chars(substr buf, fmt::integral_<T> fmt)
306  -> typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
307 {
308  return utoa(buf, fmt.val, fmt.radix);
309 }
310 /** format an integer unsigned type, pad with zeroes
311  * @ingroup doc_to_chars */
312 template<typename T>
313 C4_ALWAYS_INLINE auto to_chars(substr buf, fmt::integral_padded_<T> fmt)
314  -> typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
315 {
316  return utoa(buf, fmt.val, fmt.radix, fmt.num_digits);
317 }
318 
319 /** read an integer type, detecting overflow (returns false on overflow)
320  * @ingroup doc_from_chars */
321 template<class T>
322 C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_<T> wrapper)
323 {
324  if C4_LIKELY(!overflows<T>(s))
325  return atox(s, wrapper.val);
326  return false;
327 }
328 /** read an integer type, detecting overflow (returns false on overflow)
329  * @ingroup doc_from_chars */
330 template<class T>
331 C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_<T> *wrapper)
332 {
333  if C4_LIKELY(!overflows<T>(s))
334  return atox(s, wrapper->val);
335  return false;
336 }
337 
338 
339 //-----------------------------------------------------------------------------
340 //-----------------------------------------------------------------------------
341 //-----------------------------------------------------------------------------
342 // formatting real types
343 
344 namespace fmt {
345 
346 /** @addtogroup doc_format_specifiers
347  * @{ */
348 
349 /** @defgroup doc_real_specifiers Real specifiers
350  * @{ */
351 
352 template<class T>
353 struct real_
354 {
355  T val;
358  real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {}
359 };
360 
361 template<class T>
362 real_<T> real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
363 {
364  return real_<T>(val, precision, fmt);
365 }
366 
367 /** @} */ // real_specifiers
368 
369 /** @} */ // format_specifiers
370 
371 } // namespace fmt
372 
373 /** @ingroup doc_to_chars */
374 inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); }
375 /** @ingroup doc_to_chars */
376 inline size_t to_chars(substr buf, fmt::real_<double> fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); }
377 
378 
379 //-----------------------------------------------------------------------------
380 //-----------------------------------------------------------------------------
381 //-----------------------------------------------------------------------------
382 // writing raw binary data
383 
384 namespace fmt {
385 
386 /** @addtogroup doc_format_specifiers
387  * @{ */
388 
389 /** @defgroup doc_raw_binary_specifiers Raw binary data
390  * @{ */
391 
392 /** @see blob_ */
393 template<class T>
394 struct raw_wrapper_ : public blob_<T>
395 {
396  size_t alignment;
397 
398  C4_ALWAYS_INLINE raw_wrapper_(blob_<T> data, size_t alignment_) noexcept
399  :
400  blob_<T>(data),
401  alignment(alignment_)
402  {
403  C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two");
404  }
405 };
406 
409 
410 /** mark a variable to be written in raw binary format, using memcpy
411  * @see blob_ */
412 inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
413 {
414  return const_raw_wrapper(data, alignment);
415 }
416 /** mark a variable to be written in raw binary format, using memcpy
417  * @see blob_ */
418 inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
419 {
420  return const_raw_wrapper(data, alignment);
421 }
422 /** mark a variable to be written in raw binary format, using memcpy
423  * @see blob_ */
424 template<class T>
425 inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
426 {
427  return const_raw_wrapper(cblob(data), alignment);
428 }
429 /** mark a variable to be written in raw binary format, using memcpy
430  * @see blob_ */
431 template<class T>
432 inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
433 {
434  return const_raw_wrapper(cblob(data), alignment);
435 }
436 
437 /** mark a variable to be read in raw binary format, using memcpy */
438 inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t))
439 {
440  return raw_wrapper(data, alignment);
441 }
442 /** mark a variable to be read in raw binary format, using memcpy */
443 template<class T>
444 inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T))
445 {
446  return raw_wrapper(blob(data), alignment);
447 }
448 
449 /** @} */ // raw_binary_specifiers
450 
451 /** @} */ // format_specifiers
452 
453 } // namespace fmt
454 
455 
456 /** write a variable in raw binary format, using memcpy
457  * @ingroup doc_to_chars */
458 C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r);
459 
460 /** read a variable in raw binary format, using memcpy
461  * @ingroup doc_from_chars */
462 C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r);
463 /** read a variable in raw binary format, using memcpy
464  * @ingroup doc_from_chars */
466 {
467  return from_chars(buf, &r);
468 }
469 
470 /** read a variable in raw binary format, using memcpy
471  * @ingroup doc_from_chars_first */
473 {
474  return from_chars(buf, r);
475 }
476 /** read a variable in raw binary format, using memcpy
477  * @ingroup doc_from_chars_first */
479 {
480  return from_chars(buf, &r);
481 }
482 
483 
484 //-----------------------------------------------------------------------------
485 //-----------------------------------------------------------------------------
486 //-----------------------------------------------------------------------------
487 // formatting aligned to left/right/center
488 
489 namespace fmt {
490 
491 /** @addtogroup doc_format_specifiers
492  * @{ */
493 
494 /** @defgroup doc_alignment_specifiers Alignment specifiers
495  * @{ */
496 
497 template<class T> struct left_ { T val; size_t width; char padchar; C4_ALWAYS_INLINE left_ (T v, size_t w, char c) noexcept : val(v), width(w), padchar(c) {} };
498 template<class T> struct center_ { T val; size_t width; char padchar; C4_ALWAYS_INLINE center_(T v, size_t w, char c) noexcept : val(v), width(w), padchar(c) {} };
499 template<class T> struct right_ { T val; size_t width; char padchar; C4_ALWAYS_INLINE right_ (T v, size_t w, char c) noexcept : val(v), width(w), padchar(c) {} };
500 
501 /** tag type to mark an argument to be aligned left.
502  * @param val the argument to be aligned left
503  * @param width the length of the field
504  * @param padchar the padding character, defaults to ' '
505  *
506  * @note This function (and the return structure) uses value semantics. To
507  * avoid copies on larger types, you can use std::cref(). For example:
508  * @code{.cpp}
509  * char buf[512];
510  * std::string large_string = .....;
511  * size_t len = cat(buf, fmt::left(std::cref(large_string), 100));
512  * @endcode
513  */
514 template<class T>
515 left_<T> left(T val, size_t width, char padchar=' ')
516 {
517  return left_<T>(val, width, padchar);
518 }
519 
520 /** tag function to mark an argument to be aligned center
521  * @param val the argument to be aligned center
522  * @param width the length of the field
523  * @param padchar the padding character, defaults to ' '
524  *
525  * @note This function (and the return value) uses value semantics. To
526  * avoid copies on larger types, you can use std::cref(). For example:
527  * @code{.cpp}
528  * char buf[512];
529  * std::string large_string = .....;
530  * size_t len = cat(buf, fmt::center(std::cref(large_string), 100));
531  * @endcode
532  */
533 template<class T>
534 center_<T> center(T val, size_t width, char padchar=' ')
535 {
536  return center_<T>(val, width, padchar);
537 }
538 
539 /** tag function to mark an argument to be aligned right
540  * @param val the argument to be aligned right
541  * @param width the length of the field
542  * @param padchar the padding character, defaults to ' '
543  *
544  * @note This function (and the return value) uses value semantics. To
545  * avoid copies on larger types, you can use std::cref(). For example:
546  * @code{.cpp}
547  * char buf[512];
548  * std::string large_string = .....;
549  * size_t len = cat(buf, fmt::right(std::cref(large_string), 100));
550  * @endcode
551  */
552 template<class T>
553 right_<T> right(T val, size_t width, char padchar=' ')
554 {
555  return right_<T>(val, width, padchar);
556 }
557 
558 /** @} */ // alignment_specifiers
559 
560 /** @} */ // format_specifiers
561 
562 } // namespace fmt
563 
564 
565 /** @ingroup doc_to_chars */
566 template<class T>
567 size_t to_chars(substr buf, fmt::left_<T> const& C4_RESTRICT align)
568 {
569  size_t ret = to_chars(buf, align.val);
570  if(ret >= buf.len || ret >= align.width)
571  return ret > align.width ? ret : align.width;
572  buf.first(align.width).sub(ret).fill(align.padchar);
573  return align.width;
574 }
575 
576 /** @ingroup doc_to_chars */
577 template<class T>
578 size_t to_chars(substr buf, fmt::right_<T> const& C4_RESTRICT align)
579 {
580  size_t ret = to_chars(buf, align.val);
581  if(ret >= buf.len || ret >= align.width)
582  return ret > align.width ? ret : align.width;
583  size_t rem = align.width - ret;
584  if(ret)
585  memmove(buf.str + rem, buf.str, ret);
586  buf.first(rem).fill(align.padchar);
587  return align.width;
588 }
589 
590 /** @ingroup doc_to_chars */
591 template<class T>
592 size_t to_chars(substr buf, fmt::center_<T> const& C4_RESTRICT align)
593 {
594  size_t ret = to_chars(buf, align.val);
595  if(ret >= buf.len || ret >= align.width)
596  return ret > align.width ? ret : align.width;
597  size_t first = (align.width - ret) / 2u;
598  if(ret)
599  memmove(buf.str + first, buf.str, ret);
600  buf.first(first).fill(align.padchar);
601  buf.sub(first + ret).fill(align.padchar);
602  return align.width;
603 }
604 
605 
606 //-----------------------------------------------------------------------------
607 //-----------------------------------------------------------------------------
608 //-----------------------------------------------------------------------------
609 
610 /** @defgroup doc_cat cat: concatenate arguments to string
611  * @{ */
612 
613 
614 /** @cond dev */
615 // terminates the variadic recursion
616 inline size_t cat(substr /*buf*/)
617 {
618  return 0;
619 }
620 /** @endcond */
621 
622 
623 /** serialize the arguments, concatenating them to the given fixed-size buffer.
624  * The buffer size is strictly respected: no writes will occur beyond its end.
625  * @return the number of characters needed to write all the arguments into the buffer.
626  * @see @ref c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired
627  * @see @ref c4::uncat() for the inverse function
628  * @see @ref c4::catsep() if a separator between each argument is to be used
629  * @see @ref c4::format() if a format string is desired
630  *
631  * @note The arguments to format are restricted (legal because they
632  * are rvalues). This may require a workaround when arguments of type
633  * char[]/const char[] are passed repeatedly to the function. For
634  * example,
635  * @code{.cpp}
636  * const char str[] = "Hi! ";
637  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
638  * @endcode
639  * It is possible to work around the problem by suppressing -Wrestrict
640  * or by using the decayed type char* or const char*, or even wrapping
641  * the argument in a csubstr():
642  * @code{.cpp}
643  * const char str[] = "Hi! ";
644  * csubstr ss = to_csubstr(str);
645  * cat(buf, ss, ss, ss); // ok! compiles cleanly
646  * @endcode
647  */
648 template<class Arg, class... Args>
649 size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
650 {
651  size_t num = to_chars(buf, a);
652  buf = buf.len >= num ? buf.sub(num) : substr{};
653  num += cat(buf, more...);
654  return num;
655 }
656 
657 /** like @ref c4::cat() but return a substr instead of a size */
658 template<class... Args>
659 substr cat_sub(substr buf, Args const& C4_RESTRICT ...args)
660 {
661  size_t sz = cat(buf, args...);
662  C4_CHECK(sz <= buf.len);
663  return {buf.str, sz <= buf.len ? sz : buf.len};
664 }
665 
666 /** @} */
667 
668 
669 //-----------------------------------------------------------------------------
670 
671 
672 /** @defgroup doc_uncat uncat: read concatenated arguments from string
673  * @{ */
674 
675 /** @cond dev */
676 // terminates the variadic recursion
677 inline size_t uncat(csubstr /*buf*/)
678 {
679  return 0;
680 }
681 /** @endcond */
682 
683 
684 /** deserialize the arguments from the given buffer.
685  *
686  * @return the number of characters read from the buffer, or csubstr::npos
687  * if a conversion was not successful.
688  * @see @ref c4::cat(). @ref c4::uncat() is the inverse of @ref c4::cat(). */
689 template<class Arg, class... Args>
690 size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
691 {
692  size_t out = from_chars_first(buf, &a);
693  if C4_UNLIKELY(out == csubstr::npos)
694  return csubstr::npos;
695  buf = buf.len >= out ? buf.sub(out) : substr{};
696  size_t num = uncat(buf, more...);
697  if C4_UNLIKELY(num == csubstr::npos)
698  return csubstr::npos;
699  return out + num;
700 }
701 
702 /** @} */
703 
704 
705 
706 //-----------------------------------------------------------------------------
707 //-----------------------------------------------------------------------------
708 //-----------------------------------------------------------------------------
709 
710 
711 /** @defgroup doc_catsep catsep: cat arguments to string with separator
712  * @{ */
713 
714 /** @cond dev */
715 namespace detail {
716 template<class Sep>
717 C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
718 {
719  return 0;
720 }
721 
722 template<class Sep, class Arg, class... Args>
723 size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
724 {
725  size_t ret = to_chars(buf, sep);
726  size_t num = ret;
727  buf = buf.len >= ret ? buf.sub(ret) : substr{};
728  ret = to_chars(buf, a);
729  num += ret;
730  buf = buf.len >= ret ? buf.sub(ret) : substr{};
731  ret = catsep_more(buf, sep, more...);
732  num += ret;
733  return num;
734 }
735 } // namespace detail
736 
737 // terminates the variadic recursion
738 template<class Sep>
739 size_t catsep(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
740 {
741  return 0;
742 }
743 /** @endcond */
744 
745 
746 /** serialize the arguments, concatenating them to the given fixed-size
747  * buffer, using a separator between each argument.
748  * The buffer size is strictly respected: no writes will occur beyond its end.
749  * @return the number of characters needed to write all the arguments into the buffer.
750  * @see @ref c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired
751  * @see @ref c4::uncatsep() for the inverse function (ie, reading instead of writing)
752  * @see @ref c4::cat() if no separator is needed
753  * @see @ref c4::format() if a format string is desired
754  *
755  *
756  * @note The arguments to format are restricted (legal because they
757  * are rvalues). This may require a workaround when arguments of type
758  * char[]/const char[] are passed repeatedly to the function. For
759  * example,
760  * @code{.cpp}
761  * const char str[] = "Hi! ";
762  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
763  * @endcode
764  * It is possible to work around the problem by suppressing -Wrestrict
765  * or by using the decayed type char* or const char*, or even wrapping
766  * the argument in a csubstr():
767  * @code{.cpp}
768  * const char str[] = "Hi! ";
769  * csubstr ss = to_csubstr(str);
770  * cat(buf, ss, ss, ss); // ok! compiles cleanly
771  * @endcode
772  */
773 template<class Sep, class Arg, class... Args>
774 size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
775 {
776  size_t num = to_chars(buf, a);
777  buf = buf.len >= num ? buf.sub(num) : substr{};
778  num += detail::catsep_more(buf, sep, more...);
779  return num;
780 }
781 
782 /** like @ref c4::catsep() but return a substr instead of a size @see
783  * @ref c4::catsep(). @ref c4::uncatsep() is the inverse of @ref
784  * c4::catsep(). */
785 template<class... Args>
786 substr catsep_sub(substr buf, Args && ...args)
787 {
788  size_t sz = catsep(buf, std::forward<Args>(args)...);
789  C4_CHECK(sz <= buf.len);
790  return {buf.str, sz <= buf.len ? sz : buf.len};
791 }
792 
793 /** @} */
794 
795 
796 
797 //-----------------------------------------------------------------------------
798 //-----------------------------------------------------------------------------
799 //-----------------------------------------------------------------------------
800 
801 /** @defgroup doc_uncatsep uncatsep: deserialize the separated arguments from a string
802  * @{ */
803 
804 /** @cond dev */
805 template<class Arg>
806 inline size_t uncatsep(csubstr buf, csubstr /*sep*/, Arg &C4_RESTRICT a)
807 {
808  return from_chars(buf, &a) ? buf.len : csubstr::npos;
809 }
810 /** @endcond */
811 
812 /** deserialize the arguments from the given buffer, using a separator.
813  *
814  * @return the number of characters read from the buffer, or csubstr::npos
815  * if a conversion was not successful
816  *
817  * @see c4::catsep(). @ref c4::uncatsep() is the inverse of @ref c4::catsep(). */
818 template<class Arg, class... Args>
819 size_t uncatsep(csubstr buf, csubstr sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
820 {
821  if C4_LIKELY(sep.len > 0)
822  {
823  size_t pos = buf.find(sep);
824  if C4_LIKELY(pos != csubstr::npos)
825  {
826  if C4_LIKELY(from_chars(buf.first(pos), &a))
827  {
828  pos += sep.len;
829  size_t num = uncatsep(buf.sub(pos), sep, more...);
830  if C4_LIKELY(num != csubstr::npos)
831  return pos + num;
832  }
833  }
834  }
835  return csubstr::npos;
836 }
837 
838 /** @} */
839 
840 
841 //-----------------------------------------------------------------------------
842 //-----------------------------------------------------------------------------
843 //-----------------------------------------------------------------------------
844 
845 /** @defgroup doc_format format: formatted string interpolation
846  * @{ */
847 
848 /// @cond dev
849 // terminates the variadic recursion
850 inline size_t format(substr buf, csubstr fmt)
851 {
852  return to_chars(buf, fmt);
853 }
854 /// @endcond
855 
856 
857 /** Using a format string, serialize the arguments into the given
858  * fixed-size buffer. The buffer size is strictly respected: no writes
859  * will occur beyond its end. In the format string, each argument is
860  * marked with a compact curly-bracket pair "{}". This pair does not
861  * take any interior sequence numbers or extra formatting arguments
862  * inside it (contrary to eg the C++20 std::format implementation or
863  * the Python formatting facilities). To enable argument
864  * customization, use the formatting facilities in @ref
865  * doc_format_specifiers wrapping the arguments passed to this
866  * function.
867  *
868  * @return the number of bytes needed to write into the buffer.
869  *
870  * @see @ref c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired
871  * @see @ref c4::unformat() for the inverse function
872  * @see @ref c4::cat() if no format or separator is needed
873  * @see @ref c4::catsep() if no format is needed, but a separator must be used
874  *
875  * For example:
876  * @code{.cpp}
877  * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers
878  * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees
879  * @endcode
880  *
881  * Using @ref
882  * doc_format_specifiers enables control of the result. For example:
883  * @code{.cpp}
884  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::real(5, 3), "beers"); // the partier drank 5.000 beers
885  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::zpad(5, 3), "beers"); // the partier drank 005 beers
886  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::bin(5), "beers"); // the partier drank 0b101 beers
887  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::oct(5), "beers"); // the partier drank 0o6 beers
888  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::hex(5), "beers"); // the partier drank 0x6 beers
889  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::real(6, 3), "coffees"); // the programmer drank 6.000 coffees
890  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::zpad(6, 3), "coffees"); // the programmer drank 006 coffees
891  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::bin(6), "coffees"); // the programmer drank 0b110 coffees
892  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::oct(6), "coffees"); // the programmer drank 0o6 coffees
893  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::hex(6), "coffees"); // the programmer drank 0x6 coffees
894  * @endcode
895  *
896  * @note Arguments beyond the last curly bracket pair are silently
897  * ignored. Curly bracket pairs without a corresponding argument are
898  * printed as part of the result.
899  * @code{.cpp}
900  * // note "and nothing else" being ignored
901  * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers", "and nothing else"); // the partier drank 5 beers
902  *
903  * // note "this is ignored {}" being part of the result
904  * c4::format(buf, "the {} drank {} {} this is ignored: {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees this is ignored: {}
905  * @endcode
906  *
907  * @note The curly bracket pair cannot be escaped, but can of course
908  * be placed into the result by passing an "{}" argument in its place,
909  * or if it is provided beyond the last argument passed to the
910  * function (see prior note).
911  * @code{.cpp}
912  * // as above: no argument given, so no substitution made:
913  * c4::format(buf, "let's show {} on the result"); // let's show {} on the result
914  * // or just pass "{}" as an argument to force the substitution:
915  * c4::format(buf, "let's show {} on the result and then {}", "{}", "this"); // let's show {} on the result and then this
916  * @endcode
917  *
918  * @note The arguments to format are restricted (legal because they
919  * are rvalues). This may require a workaround when arguments of type
920  * char[]/const char[] are passed repeatedly to the function. For
921  * example,
922  * @code{.cpp}
923  * const char str[] = "Hi! ";
924  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
925  * @endcode
926  * It is possible to work around the problem by suppressing -Wrestrict
927  * or by using the decayed type char* or const char*, or even wrapping
928  * the argument in a csubstr():
929  * @code{.cpp}
930  * const char str[] = "Hi! ";
931  * csubstr ss = to_csubstr(str);
932  * cat(buf, ss, ss, ss); // ok! compiles cleanly
933  * @endcode
934  */
935 template<class Arg, class... Args>
936 size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
937 {
938  size_t pos = fmt.find("{}");
939  if C4_UNLIKELY(pos == csubstr::npos)
940  return to_chars(buf, fmt);
941  size_t num = to_chars(buf, fmt.first(pos));
942  size_t out = num;
943  buf = buf.len >= num ? buf.sub(num) : substr{};
944  num = to_chars(buf, a);
945  out += num;
946  buf = buf.len >= num ? buf.sub(num) : substr{};
947  num = format(buf, fmt.sub(pos + 2), more...);
948  out += num;
949  return out;
950 }
951 
952 /** like @ref c4::format() but return a substr instead of a size
953  * @see c4::format()
954  * @see c4::catsep(). @ref c4::uncatsep() is the inverse of @ref c4::catsep(). */
955 template<class... Args>
956 substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args)
957 {
958  size_t sz = c4::format(buf, fmt, args...);
959  C4_CHECK(sz <= buf.len);
960  return {buf.str, sz <= buf.len ? sz : buf.len};
961 }
962 
963 /** @} */
964 
965 
966 //-----------------------------------------------------------------------------
967 
968 /** @defgroup doc_unformat unformat: formatted read from string
969  * @{ */
970 
971 /// @cond dev
972 // terminates the variadic recursion
973 inline size_t unformat(csubstr /*buf*/, csubstr fmt)
974 {
975  return fmt.len;
976 }
977 /// @endcond
978 
979 
980 /** using a format string, deserialize the arguments from the given
981  * buffer. This is the inverse function to @ref c4::format().
982  *
983  * @return the number of characters read from the buffer, or npos if a conversion failed.
984  *
985  * @see @ref c4::format(). */
986 template<class Arg, class... Args>
987 size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
988 {
989  const size_t pos = fmt.find("{}");
990  if C4_UNLIKELY(pos == csubstr::npos)
991  return unformat(buf, fmt);
992  size_t num = pos;
993  size_t out = num;
994  buf = buf.len >= num ? buf.sub(num) : substr{};
995  num = from_chars_first(buf, &a);
996  if C4_UNLIKELY(num == csubstr::npos)
997  return csubstr::npos;
998  out += num;
999  buf = buf.len >= num ? buf.sub(num) : substr{};
1000  num = unformat(buf, fmt.sub(pos + 2), more...);
1001  if C4_UNLIKELY(num == csubstr::npos)
1002  return csubstr::npos;
1003  out += num;
1004  return out;
1005 }
1006 
1007 /** @} */
1008 
1009 
1010 //-----------------------------------------------------------------------------
1011 //-----------------------------------------------------------------------------
1012 //-----------------------------------------------------------------------------
1013 
1014 /** cat+resize: like @ref c4::cat(), but receives a container, and
1015  * resizes it as needed to contain the result. The container is
1016  * overwritten. To append to it, use @ref c4::catrs_append().
1017  *
1018  * @see @ref c4::cat()
1019  * @see @ref c4::catrs_append()
1020  * @ingroup doc_cat
1021  *
1022  * @note The arguments to format are restricted (legal because they
1023  * are rvalues). This may require a workaround when arguments of type
1024  * char[]/const char[] are passed repeatedly to the function. For
1025  * example,
1026  * @code{.cpp}
1027  * const char str[] = "Hi! ";
1028  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1029  * @endcode
1030  * It is possible to work around the problem by suppressing -Wrestrict
1031  * or by using the decayed type char* or const char*, or even wrapping
1032  * the argument in a csubstr():
1033  * @code{.cpp}
1034  * const char str[] = "Hi! ";
1035  * csubstr ss = to_csubstr(str);
1036  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1037  * @endcode
1038  */
1039 template<class CharOwningContainer, class... Args>
1040 inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
1041 {
1042  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1043 retry:
1044  substr buf = to_substr(*cont);
1045  size_t ret = cat(buf, args...);
1046  cont->resize(ret);
1047  if(ret > buf.len)
1048  goto retry;
1049 }
1050 
1051 /** cat+resize: like @ref c4::cat(), but creates and returns a new
1052  * container sized as needed to contain the result.
1053  *
1054  * @see @ref c4::cat()
1055  * @ingroup doc_cat
1056  *
1057  * @note The arguments to format are restricted (legal because they
1058  * are rvalues). This may require a workaround when arguments of type
1059  * char[]/const char[] are passed repeatedly to the function. For
1060  * example,
1061  * @code{.cpp}
1062  * const char str[] = "Hi! ";
1063  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1064  * @endcode
1065  * It is possible to work around the problem by suppressing -Wrestrict
1066  * or by using the decayed type char* or const char*, or even wrapping
1067  * the argument in a csubstr():
1068  * @code{.cpp}
1069  * const char str[] = "Hi! ";
1070  * csubstr ss = to_csubstr(str);
1071  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1072  * @endcode
1073  */
1074 template<class CharOwningContainer, class... Args>
1075 inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args)
1076 {
1077  CharOwningContainer cont;
1078  catrs(&cont, args...);
1079  return cont;
1080 }
1081 
1082 /** cat+resize+append: like @ref c4::cat(), but receives a container,
1083  * and appends to it instead of overwriting it. The container is
1084  * resized as needed to contain the result.
1085  *
1086  * @return the region newly appended to the original container
1087  * @see @ref c4::cat()
1088  * @see @ref c4::catrs()
1089  * @ingroup doc_cat
1090  *
1091  * @note The arguments to format are restricted (legal because they
1092  * are rvalues). This may require a workaround when arguments of type
1093  * char[]/const char[] are passed repeatedly to the function. For
1094  * example,
1095  * @code{.cpp}
1096  * const char str[] = "Hi! ";
1097  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1098  * @endcode
1099  * It is possible to work around the problem by suppressing -Wrestrict
1100  * or by using the decayed type char* or const char*, or even wrapping
1101  * the argument in a csubstr():
1102  * @code{.cpp}
1103  * const char str[] = "Hi! ";
1104  * csubstr ss = to_csubstr(str);
1105  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1106  * @endcode
1107  */
1108 template<class CharOwningContainer, class... Args>
1109 inline csubstr catrs_append(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
1110 {
1111  const size_t pos = cont->size();
1112  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1113 retry:
1114  substr buf = to_substr(*cont).sub(pos);
1115  size_t ret = cat(buf, args...);
1116  cont->resize(pos + ret);
1117  if(ret > buf.len)
1118  goto retry;
1119  return to_csubstr(*cont).range(pos, cont->size());
1120 }
1121 
1122 
1123 //-----------------------------------------------------------------------------
1124 
1125 /** catsep+resize: like @ref c4::catsep(), but receives a container,
1126  * and resizes it as needed to contain the result. The container is
1127  * overwritten. To append to the container use @ref
1128  * c4::catseprs_append().
1129  *
1130  * @see @ref c4::catsep()
1131  * @see @ref c4::catseprs_append()
1132  * @ingroup doc_catsep
1133  *
1134  * @note The arguments to format are restricted (legal because they
1135  * are rvalues). This may require a workaround when arguments of type
1136  * char[]/const char[] are passed repeatedly to the function. For
1137  * example,
1138  * @code{.cpp}
1139  * const char str[] = "Hi! ";
1140  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1141  * @endcode
1142  * It is possible to work around the problem by suppressing -Wrestrict
1143  * or by using the decayed type char* or const char*, or even wrapping
1144  * the argument in a csubstr():
1145  * @code{.cpp}
1146  * const char str[] = "Hi! ";
1147  * csubstr ss = to_csubstr(str);
1148  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1149  * @endcode
1150  */
1151 template<class CharOwningContainer, class Sep, class... Args>
1152 inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
1153 {
1154  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1155 retry:
1156  substr buf = to_substr(*cont);
1157  size_t ret = catsep(buf, sep, args...);
1158  cont->resize(ret);
1159  if(ret > buf.len)
1160  goto retry;
1161 }
1162 
1163 /** catsep+resize: like @ref c4::catsep(), but create a new container
1164  * with the result.
1165  *
1166  * @return the requested container
1167  * @ingroup doc_catsep
1168  *
1169  * @note The arguments to format are restricted (legal because they
1170  * are rvalues). This may require a workaround when arguments of type
1171  * char[]/const char[] are passed repeatedly to the function. For
1172  * example,
1173  * @code{.cpp}
1174  * const char str[] = "Hi! ";
1175  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1176  * @endcode
1177  * It is possible to work around the problem by suppressing -Wrestrict
1178  * or by using the decayed type char* or const char*, or even wrapping
1179  * the argument in a csubstr():
1180  * @code{.cpp}
1181  * const char str[] = "Hi! ";
1182  * csubstr ss = to_csubstr(str);
1183  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1184  * @endcode
1185  */
1186 template<class CharOwningContainer, class Sep, class... Args>
1187 inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
1188 {
1189  CharOwningContainer cont;
1190  catseprs(&cont, sep, args...);
1191  return cont;
1192 }
1193 
1194 
1195 /** catsep+resize+append: like @ref c4::catsep(), but receives a
1196  * container, and appends the arguments, resizing the container as
1197  * needed to contain the result. The buffer is appended to.
1198  *
1199  * @return a csubstr of the appended part
1200  * @ingroup doc_catsep
1201  *
1202  * @note The arguments to format are restricted (legal because they
1203  * are rvalues). This may require a workaround when arguments of type
1204  * char[]/const char[] are passed repeatedly to the function. For
1205  * example,
1206  * @code{.cpp}
1207  * const char str[] = "Hi! ";
1208  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1209  * @endcode
1210  * It is possible to work around the problem by suppressing -Wrestrict
1211  * or by using the decayed type char* or const char*, or even wrapping
1212  * the argument in a csubstr():
1213  * @code{.cpp}
1214  * const char str[] = "Hi! ";
1215  * csubstr ss = to_csubstr(str);
1216  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1217  * @endcode
1218  */
1219 template<class CharOwningContainer, class Sep, class... Args>
1220 inline csubstr catseprs_append(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
1221 {
1222  const size_t pos = cont->size();
1223  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1224 retry:
1225  substr buf = to_substr(*cont).sub(pos);
1226  size_t ret = catsep(buf, sep, args...);
1227  cont->resize(pos + ret);
1228  if(ret > buf.len)
1229  goto retry;
1230  return to_csubstr(*cont).range(pos, cont->size());
1231 }
1232 
1233 
1234 //-----------------------------------------------------------------------------
1235 
1236 /** format+resize: like @ref c4::format(), but receives a container,
1237  * and resizes it as needed to contain the result. The container is
1238  * overwritten. To append to the container use @ref
1239  * c4::formatrs_append().
1240  *
1241  * @see @ref c4::format()
1242  * @see @ref c4::formatrs_append()
1243  * @ingroup doc_format
1244  *
1245  * @note The arguments to format are restricted (legal because they
1246  * are rvalues). This may require a workaround when arguments of type
1247  * char[]/const char[] are passed repeatedly to the function. For
1248  * example,
1249  * @code{.cpp}
1250  * const char str[] = "Hi! ";
1251  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1252  * @endcode
1253  * It is possible to work around the problem by suppressing -Wrestrict
1254  * or by using the decayed type char* or const char*, or even wrapping
1255  * the argument in a csubstr():
1256  * @code{.cpp}
1257  * const char str[] = "Hi! ";
1258  * csubstr ss = to_csubstr(str);
1259  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1260  * @endcode
1261  */
1262 template<class CharOwningContainer, class... Args>
1263 inline void formatrs(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
1264 {
1265  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1266 retry:
1267  substr buf = to_substr(*cont);
1268  size_t ret = format(buf, fmt, args...);
1269  cont->resize(ret);
1270  if(ret > buf.len)
1271  goto retry;
1272 }
1273 
1274 /** format+resize: like @ref c4::format(), but create a new container
1275  * with the result.
1276  *
1277  * @return the requested container
1278  * @ingroup doc_format
1279  *
1280  * @note The arguments to format are restricted (legal because they
1281  * are rvalues). This may require a workaround when arguments of type
1282  * char[]/const char[] are passed repeatedly to the function. For
1283  * example,
1284  * @code{.cpp}
1285  * const char str[] = "Hi! ";
1286  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1287  * @endcode
1288  * It is possible to work around the problem by suppressing -Wrestrict
1289  * or by using the decayed type char* or const char*, or even wrapping
1290  * the argument in a csubstr():
1291  * @code{.cpp}
1292  * const char str[] = "Hi! ";
1293  * csubstr ss = to_csubstr(str);
1294  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1295  * @endcode
1296  */
1297 template<class CharOwningContainer, class... Args>
1298 inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args)
1299 {
1300  CharOwningContainer cont;
1301  formatrs(&cont, fmt, args...);
1302  return cont;
1303 }
1304 
1305 /** format+resize+append: like @ref c4::format(), but receives a
1306  * container, and appends the arguments, resizing the container as
1307  * needed to contain the result. The buffer is appended to.
1308  *
1309  * @return the region newly appended to the original container
1310  * @ingroup doc_format
1311  *
1312  * @note The arguments to format are restricted (legal because they
1313  * are rvalues). This may require a workaround when arguments of type
1314  * char[]/const char[] are passed repeatedly to the function. For
1315  * example,
1316  * @code{.cpp}
1317  * const char str[] = "Hi! ";
1318  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1319  * @endcode
1320  * It is possible to work around the problem by suppressing -Wrestrict
1321  * or by using the decayed type char* or const char*, or even wrapping
1322  * the argument in a csubstr():
1323  * @code{.cpp}
1324  * const char str[] = "Hi! ";
1325  * csubstr ss = to_csubstr(str);
1326  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1327  * @endcode
1328  */
1329 template<class CharOwningContainer, class... Args>
1330 inline csubstr formatrs_append(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
1331 {
1332  const size_t pos = cont->size();
1333  C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Warray-bounds")
1334  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1335  C4_SUPPRESS_WARNING_GCC_POP
1336 retry:
1337  substr buf = to_substr(*cont).sub(pos);
1338  size_t ret = format(buf, fmt, args...);
1339  cont->resize(pos + ret);
1340  if(ret > buf.len)
1341  goto retry;
1342  return to_csubstr(*cont).range(pos, cont->size());
1343 }
1344 
1345 /** @} */
1346 
1347 } // namespace c4
1348 
1349 // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast,*avoid-goto*)
1350 #ifdef _MSC_VER
1351 # pragma warning(pop)
1352 #elif defined(__clang__)
1353 # pragma clang diagnostic pop
1354 #elif defined(__GNUC__)
1355 # pragma GCC diagnostic pop
1356 #endif
1357 
1358 #endif /* C4_FORMAT_HPP_ */
left_(T v, size_t w, char c) noexcept
Definition: format.hpp:497
csubstr formatrs_append(CharOwningContainer *cont, csubstr fmt, Args const &...args)
format+resize+append: like c4::format(), but receives a container, and appends the arguments...
Definition: format.hpp:1330
center_(T v, size_t w, char c) noexcept
Definition: format.hpp:498
void catseprs(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize: like c4::catsep(), but receives a container, and resizes it as needed to contain the r...
Definition: format.hpp:1152
size_t width
Definition: format.hpp:499
format an integral type with a custom radix, and pad with zeroes on the left
Definition: format.hpp:116
size_t uncatsep(csubstr buf, csubstr sep, Arg &a, Args &...more)
deserialize the arguments from the given buffer, using a separator.
Definition: format.hpp:819
C4_STATIC_ASSERT(std::is_integral< T >::value)
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:2097
size_t unformat(csubstr buf, csubstr fmt, Arg &a, Args &...more)
using a format string, deserialize the arguments from the given buffer.
Definition: format.hpp:987
right_(T v, size_t w, char c) noexcept
Definition: format.hpp:499
bool atox(csubstr s, uint8_t *v) noexcept
Definition: charconv.hpp:2302
substr format_sub(substr buf, csubstr fmt, Args const &...args)
like c4::format() but return a substr instead of a size
Definition: format.hpp:956
C * str
a restricted pointer to the first character of the substring
Definition: substr.hpp:216
size_t format(substr buf, csubstr fmt, Arg const &a, Args const &...more)
Using a format string, serialize the arguments into the given fixed-size buffer.
Definition: format.hpp:936
auto fill(C val) -> typename std::enable_if< !std::is_const< U >::value, void >::type
fill the entire contents with the given val
Definition: substr.hpp:2153
substr to_substr(char(&s)[N]) noexcept
Definition: substr.hpp:2376
integral_< intptr_t > hex(std::nullptr_t)
format null as an hexadecimal value
Definition: format.hpp:160
void catrs(CharOwningContainer *cont, Args const &...args)
cat+resize: like c4::cat(), but receives a container, and resizes it as needed to contain the result...
Definition: format.hpp:1040
const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
mark a variable to be written in raw binary format, using memcpy
Definition: format.hpp:412
size_t cat(substr buf, Arg const &a, Args const &...more)
serialize the arguments, concatenating them to the given fixed-size buffer.
Definition: format.hpp:649
print the real number in floating point format (like f)
Definition: charconv.hpp:194
basic_substring first(size_t num) const noexcept
return the first num elements: [0,num[
Definition: substr.hpp:529
real_< T > real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
Definition: format.hpp:362
size_t width
Definition: format.hpp:497
bool from_chars(csubstr buf, uint8_t *v) noexcept
Definition: charconv.hpp:2383
size_t utoa(substr buf, T v) noexcept
convert an integral unsigned decimal to a string.
Definition: charconv.hpp:1315
integral_< intptr_t > bin(std::nullptr_t)
format null as a binary 0-1 value
Definition: format.hpp:212
size_t size() const noexcept
Definition: substr.hpp:358
integral_< intptr_t > oct(std::nullptr_t)
format null as an octal value
Definition: format.hpp:185
basic_substring sub(size_t first) const noexcept
return [first,len[
Definition: substr.hpp:502
integral_< T > integral(T val, T radix=10)
format an integral type with a custom radix
Definition: format.hpp:128
raw_wrapper_(blob_< T > data, size_t alignment_) noexcept
Definition: format.hpp:398
Lightweight generic type-safe wrappers for converting individual values to/from strings.
csubstr to_csubstr(const char(&s)[N]) noexcept
Definition: substr.hpp:2380
basic_substring< char > substr
a mutable string view
Definition: substr.hpp:2355
basic_substring< const char > csubstr
an immutable string view
Definition: substr.hpp:2356
substr cat_sub(substr buf, Args const &...args)
like c4::cat() but return a substr instead of a size
Definition: format.hpp:659
size_t from_chars_first(csubstr buf, uint8_t *v) noexcept
Definition: charconv.hpp:2411
csubstr catrs_append(CharOwningContainer *cont, Args const &...args)
cat+resize+append: like c4::cat(), but receives a container, and appends to it instead of overwriting...
Definition: format.hpp:1109
center_< T > center(T val, size_t width, char padchar=' ')
tag function to mark an argument to be aligned center
Definition: format.hpp:534
size_t itoa(substr buf, T v) noexcept
convert an integral signed decimal to a string.
Definition: charconv.hpp:1120
basic_substring range(size_t first, size_t last=npos) const noexcept
return [first,last[.
Definition: substr.hpp:519
raw_wrapper_< cbyte > const_raw_wrapper
Definition: format.hpp:407
format an integral type with a custom radix
Definition: format.hpp:106
size_t len
the length of the substring
Definition: substr.hpp:218
csubstr catseprs_append(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize+append: like c4::catsep(), but receives a container, and appends the arguments...
Definition: format.hpp:1220
size_t catsep(substr buf, Sep const &sep, Arg const &a, Args const &...more)
serialize the arguments, concatenating them to the given fixed-size buffer, using a separator between...
Definition: format.hpp:774
RealFormat_e
Definition: charconv.hpp:192
void formatrs(CharOwningContainer *cont, csubstr fmt, Args const &...args)
format+resize: like c4::format(), but receives a container, and resizes it as needed to contain the r...
Definition: format.hpp:1263
integral_padded_(T val_, T radix_, size_t nd)
Definition: format.hpp:122
real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT)
Definition: format.hpp:358
boolalpha_ boolalpha(T const &val=false)
tag function to mark a variable to be written as an alphabetic boolean, ie as either true or false ...
Definition: format.hpp:70
integral_(T val_, T radix_)
Definition: format.hpp:111
C4_STATIC_ASSERT(std::is_integral< T >::value)
size_t find(const C c, size_t start_pos=0) const
Definition: substr.hpp:713
RealFormat_e fmt
Definition: format.hpp:357
substr catsep_sub(substr buf, Args &&...args)
like c4::catsep() but return a substr instead of a size
Definition: format.hpp:786
overflow_checked_< T > overflow_checked(T &val)
Definition: format.hpp:273
integral_padded_< T > zpad(T val, size_t num_digits)
pad the argument with zeroes on the left, with decimal radix
Definition: format.hpp:232
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:2071
size_t to_chars(substr buf, uint8_t v) noexcept
Definition: charconv.hpp:2342
left_< T > left(T val, size_t width, char padchar=' ')
tag type to mark an argument to be aligned left.
Definition: format.hpp:515
const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
mark a variable to be written in raw binary format, using memcpy
Definition: format.hpp:418
raw_wrapper_< byte > raw_wrapper
Definition: format.hpp:408
right_< T > right(T val, size_t width, char padchar=' ')
tag function to mark an argument to be aligned right
Definition: format.hpp:553
size_t uncat(csubstr buf, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:690