rapidyaml  0.13.0
parse and emit YAML, and do it fast
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
289 typename std::enable_if<std::is_signed<T>::value, size_t>::type
290 to_chars(substr buf, fmt::integral_<T> fmt)
291 {
292  return itoa(buf, fmt.val, fmt.radix);
293 }
294 /** format an integer signed type, pad with zeroes
295  * @ingroup doc_to_chars */
296 template<typename T>
297 C4_ALWAYS_INLINE
298 typename std::enable_if<std::is_signed<T>::value, size_t>::type
300 {
301  return itoa(buf, fmt.val, fmt.radix, fmt.num_digits);
302 }
303 
304 /** format an integer unsigned type
305  * @ingroup doc_to_chars */
306 template<typename T>
307 C4_ALWAYS_INLINE
308 typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
309 to_chars(substr buf, fmt::integral_<T> fmt)
310 {
311  return utoa(buf, fmt.val, fmt.radix);
312 }
313 /** format an integer unsigned type, pad with zeroes
314  * @ingroup doc_to_chars */
315 template<typename T>
316 C4_ALWAYS_INLINE
317 typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
319 {
320  return utoa(buf, fmt.val, fmt.radix, fmt.num_digits);
321 }
322 
323 /** read an integer type, detecting overflow (returns false on overflow)
324  * @ingroup doc_from_chars */
325 template<class T>
326 C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_<T> wrapper)
327 {
328  if(C4_LIKELY(!overflows<T>(s)))
329  return atox(s, wrapper.val);
330  return false;
331 }
332 /** read an integer type, detecting overflow (returns false on overflow)
333  * @ingroup doc_from_chars */
334 template<class T>
335 C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_<T> *wrapper)
336 {
337  if(C4_LIKELY(!overflows<T>(s)))
338  return atox(s, wrapper->val);
339  return false;
340 }
341 
342 
343 //-----------------------------------------------------------------------------
344 //-----------------------------------------------------------------------------
345 //-----------------------------------------------------------------------------
346 // formatting real types
347 
348 namespace fmt {
349 
350 /** @addtogroup doc_format_specifiers
351  * @{ */
352 
353 /** @defgroup doc_real_specifiers Real specifiers
354  * @{ */
355 
356 template<class T>
357 struct real_
358 {
359  T val;
362  real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {}
363 };
364 
365 template<class T>
366 real_<T> real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
367 {
368  return real_<T>(val, precision, fmt);
369 }
370 
371 /** @} */ // real_specifiers
372 
373 /** @} */ // format_specifiers
374 
375 } // namespace fmt
376 
377 /** @ingroup doc_to_chars */
378 inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); }
379 /** @ingroup doc_to_chars */
380 inline size_t to_chars(substr buf, fmt::real_<double> fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); }
381 
382 
383 //-----------------------------------------------------------------------------
384 //-----------------------------------------------------------------------------
385 //-----------------------------------------------------------------------------
386 // writing raw binary data
387 
388 namespace fmt {
389 
390 /** @addtogroup doc_format_specifiers
391  * @{ */
392 
393 /** @defgroup doc_raw_binary_specifiers Raw binary data
394  * @{ */
395 
396 /** @see blob_ */
397 template<class T>
398 struct raw_wrapper_ : public blob_<T>
399 {
400  size_t alignment;
401 
402  C4_ALWAYS_INLINE raw_wrapper_(blob_<T> data, size_t alignment_) noexcept
403  :
404  blob_<T>(data),
405  alignment(alignment_)
406  {
407  C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two");
408  }
409 };
410 
413 
414 /** mark a variable to be written in raw binary format, using memcpy
415  * @see blob_ */
416 inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
417 {
418  return const_raw_wrapper(data, alignment);
419 }
420 /** mark a variable to be written in raw binary format, using memcpy
421  * @see blob_ */
422 inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
423 {
424  return const_raw_wrapper(data, alignment);
425 }
426 /** mark a variable to be written in raw binary format, using memcpy
427  * @see blob_ */
428 template<class T>
429 inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
430 {
431  return const_raw_wrapper(cblob(data), alignment);
432 }
433 /** mark a variable to be written in raw binary format, using memcpy
434  * @see blob_ */
435 template<class T>
436 inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
437 {
438  return const_raw_wrapper(cblob(data), alignment);
439 }
440 
441 /** mark a variable to be read in raw binary format, using memcpy */
442 inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t))
443 {
444  return raw_wrapper(data, alignment);
445 }
446 /** mark a variable to be read in raw binary format, using memcpy */
447 template<class T>
448 inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T))
449 {
450  return raw_wrapper(blob(data), alignment);
451 }
452 
453 /** @} */ // raw_binary_specifiers
454 
455 /** @} */ // format_specifiers
456 
457 } // namespace fmt
458 
459 
460 /** write a variable in raw binary format, using memcpy
461  * @ingroup doc_to_chars */
462 C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r);
463 
464 /** read a variable in raw binary format, using memcpy
465  * @ingroup doc_from_chars */
466 C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r);
467 /** read a variable in raw binary format, using memcpy
468  * @ingroup doc_from_chars */
469 inline bool from_chars(csubstr buf, fmt::raw_wrapper r)
470 {
471  return from_chars(buf, &r);
472 }
473 
474 /** read a variable in raw binary format, using memcpy
475  * @ingroup doc_from_chars_first */
476 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper *r)
477 {
478  return from_chars(buf, r);
479 }
480 /** read a variable in raw binary format, using memcpy
481  * @ingroup doc_from_chars_first */
482 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r)
483 {
484  return from_chars(buf, &r);
485 }
486 
487 
488 //-----------------------------------------------------------------------------
489 //-----------------------------------------------------------------------------
490 //-----------------------------------------------------------------------------
491 // formatting aligned to left/right/center
492 
493 namespace fmt {
494 
495 /** @addtogroup doc_format_specifiers
496  * @{ */
497 
498 /** @defgroup doc_alignment_specifiers Alignment specifiers
499  * @{ */
500 
501 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) {} };
502 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) {} };
503 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) {} };
504 
505 /** tag type to mark an argument to be aligned left.
506  * @param val the argument to be aligned left
507  * @param width the length of the field
508  * @param padchar the padding character, defaults to ' '
509  *
510  * @note This function (and the return structure) uses value semantics. To
511  * avoid copies on larger types, you can use std::cref(). For example:
512  * @code{.cpp}
513  * char buf[512];
514  * std::string large_string = .....;
515  * size_t len = cat(buf, fmt::left(std::cref(large_string), 100));
516  * @endcode
517  */
518 template<class T>
519 left_<T> left(T val, size_t width, char padchar=' ')
520 {
521  return left_<T>(val, width, padchar);
522 }
523 
524 /** tag function to mark an argument to be aligned center
525  * @param val the argument to be aligned center
526  * @param width the length of the field
527  * @param padchar the padding character, defaults to ' '
528  *
529  * @note This function (and the return value) uses value semantics. To
530  * avoid copies on larger types, you can use std::cref(). For example:
531  * @code{.cpp}
532  * char buf[512];
533  * std::string large_string = .....;
534  * size_t len = cat(buf, fmt::center(std::cref(large_string), 100));
535  * @endcode
536  */
537 template<class T>
538 center_<T> center(T val, size_t width, char padchar=' ')
539 {
540  return center_<T>(val, width, padchar);
541 }
542 
543 /** tag function to mark an argument to be aligned right
544  * @param val the argument to be aligned right
545  * @param width the length of the field
546  * @param padchar the padding character, defaults to ' '
547  *
548  * @note This function (and the return value) uses value semantics. To
549  * avoid copies on larger types, you can use std::cref(). For example:
550  * @code{.cpp}
551  * char buf[512];
552  * std::string large_string = .....;
553  * size_t len = cat(buf, fmt::right(std::cref(large_string), 100));
554  * @endcode
555  */
556 template<class T>
557 right_<T> right(T val, size_t width, char padchar=' ')
558 {
559  return right_<T>(val, width, padchar);
560 }
561 
562 /** @} */ // alignment_specifiers
563 
564 /** @} */ // format_specifiers
565 
566 } // namespace fmt
567 
568 
569 /** @ingroup doc_to_chars */
570 template<class T>
571 size_t to_chars(substr buf, fmt::left_<T> const& C4_RESTRICT align)
572 {
573  size_t ret = to_chars(buf, align.val);
574  if(ret >= buf.len || ret >= align.width)
575  return ret > align.width ? ret : align.width;
576  buf.first(align.width).sub(ret).fill(align.padchar);
577  return align.width;
578 }
579 
580 /** @ingroup doc_to_chars */
581 template<class T>
582 size_t to_chars(substr buf, fmt::right_<T> const& C4_RESTRICT align)
583 {
584  size_t ret = to_chars(buf, align.val);
585  if(ret >= buf.len || ret >= align.width)
586  return ret > align.width ? ret : align.width;
587  size_t rem = align.width - ret;
588  if(ret)
589  memmove(buf.str + rem, buf.str, ret);
590  buf.first(rem).fill(align.padchar);
591  return align.width;
592 }
593 
594 /** @ingroup doc_to_chars */
595 template<class T>
596 size_t to_chars(substr buf, fmt::center_<T> const& C4_RESTRICT align)
597 {
598  size_t ret = to_chars(buf, align.val);
599  if(ret >= buf.len || ret >= align.width)
600  return ret > align.width ? ret : align.width;
601  size_t first = (align.width - ret) / 2u;
602  if(ret)
603  memmove(buf.str + first, buf.str, ret);
604  buf.first(first).fill(align.padchar);
605  buf.sub(first + ret).fill(align.padchar);
606  return align.width;
607 }
608 
609 
610 //-----------------------------------------------------------------------------
611 //-----------------------------------------------------------------------------
612 //-----------------------------------------------------------------------------
613 
614 /** @defgroup doc_cat cat: concatenate arguments to string
615  * @{ */
616 
617 
618 /** @cond dev */
619 // terminates the variadic recursion
620 inline size_t cat(substr /*buf*/)
621 {
622  return 0;
623 }
624 /** @endcond */
625 
626 
627 /** serialize the arguments, concatenating them to the given fixed-size buffer.
628  * The buffer size is strictly respected: no writes will occur beyond its end.
629  * @return the number of characters needed to write all the arguments into the buffer.
630  * @see @ref c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired
631  * @see @ref c4::uncat() for the inverse function
632  * @see @ref c4::catsep() if a separator between each argument is to be used
633  * @see @ref c4::format() if a format string is desired
634  *
635  * @note The arguments to format are restricted (legal because they
636  * are rvalues). This may require a workaround when arguments of type
637  * char[]/const char[] are passed repeatedly to the function. For
638  * example,
639  * @code{.cpp}
640  * const char str[] = "Hi! ";
641  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
642  * @endcode
643  * It is possible to work around the problem by suppressing -Wrestrict
644  * or by using the decayed type char* or const char*, or even wrapping
645  * the argument in a csubstr():
646  * @code{.cpp}
647  * const char str[] = "Hi! ";
648  * csubstr ss = to_csubstr(str);
649  * cat(buf, ss, ss, ss); // ok! compiles cleanly
650  * @endcode
651  */
652 template<class Arg, class... Args>
653 size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
654 {
655  size_t num = to_chars(buf, a);
656  buf = buf.len >= num ? buf.sub(num) : substr{};
657  num += cat(buf, more...);
658  return num;
659 }
660 
661 /** like @ref c4::cat() but return a substr instead of a size */
662 template<class... Args>
663 substr cat_sub(substr buf, Args const& C4_RESTRICT ...args)
664 {
665  size_t sz = cat(buf, args...);
666  C4_CHECK(sz <= buf.len);
667  return {buf.str, sz <= buf.len ? sz : buf.len};
668 }
669 
670 /** @} */
671 
672 
673 //-----------------------------------------------------------------------------
674 
675 
676 /** @defgroup doc_uncat uncat: read concatenated arguments from string
677  * @{ */
678 
679 /** @cond dev */
680 // terminates the variadic recursion
681 inline size_t uncat(csubstr /*buf*/)
682 {
683  return 0;
684 }
685 /** @endcond */
686 
687 
688 /** deserialize the arguments from the given buffer.
689  *
690  * @return the number of characters read from the buffer, or csubstr::npos
691  * if a conversion was not successful.
692  * @see @ref c4::cat(). @ref c4::uncat() is the inverse of @ref c4::cat(). */
693 template<class Arg, class... Args>
694 size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
695 {
696  size_t out = from_chars_first(buf, &a);
697  if(C4_UNLIKELY(out == csubstr::npos))
698  return csubstr::npos;
699  buf = buf.len >= out ? buf.sub(out) : substr{};
700  size_t num = uncat(buf, more...);
701  if(C4_UNLIKELY(num == csubstr::npos))
702  return csubstr::npos;
703  return out + num;
704 }
705 
706 /** @} */
707 
708 
709 
710 //-----------------------------------------------------------------------------
711 //-----------------------------------------------------------------------------
712 //-----------------------------------------------------------------------------
713 
714 
715 /** @defgroup doc_catsep catsep: cat arguments to string with separator
716  * @{ */
717 
718 /** @cond dev */
719 namespace detail {
720 template<class Sep>
721 C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
722 {
723  return 0;
724 }
725 
726 template<class Sep, class Arg, class... Args>
727 size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
728 {
729  size_t ret = to_chars(buf, sep);
730  size_t num = ret;
731  buf = buf.len >= ret ? buf.sub(ret) : substr{};
732  ret = to_chars(buf, a);
733  num += ret;
734  buf = buf.len >= ret ? buf.sub(ret) : substr{};
735  ret = catsep_more(buf, sep, more...);
736  num += ret;
737  return num;
738 }
739 } // namespace detail
740 
741 // terminates the variadic recursion
742 template<class Sep>
743 size_t catsep(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
744 {
745  return 0;
746 }
747 /** @endcond */
748 
749 
750 /** serialize the arguments, concatenating them to the given fixed-size
751  * buffer, using a separator between each argument.
752  * The buffer size is strictly respected: no writes will occur beyond its end.
753  * @return the number of characters needed to write all the arguments into the buffer.
754  * @see @ref c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired
755  * @see @ref c4::uncatsep() for the inverse function (ie, reading instead of writing)
756  * @see @ref c4::cat() if no separator is needed
757  * @see @ref c4::format() if a format string is desired
758  *
759  *
760  * @note The arguments to format are restricted (legal because they
761  * are rvalues). This may require a workaround when arguments of type
762  * char[]/const char[] are passed repeatedly to the function. For
763  * example,
764  * @code{.cpp}
765  * const char str[] = "Hi! ";
766  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
767  * @endcode
768  * It is possible to work around the problem by suppressing -Wrestrict
769  * or by using the decayed type char* or const char*, or even wrapping
770  * the argument in a csubstr():
771  * @code{.cpp}
772  * const char str[] = "Hi! ";
773  * csubstr ss = to_csubstr(str);
774  * cat(buf, ss, ss, ss); // ok! compiles cleanly
775  * @endcode
776  */
777 template<class Sep, class Arg, class... Args>
778 size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
779 {
780  size_t num = to_chars(buf, a);
781  buf = buf.len >= num ? buf.sub(num) : substr{};
782  num += detail::catsep_more(buf, sep, more...);
783  return num;
784 }
785 
786 /** like @ref c4::catsep() but return a substr instead of a size @see
787  * @ref c4::catsep(). @ref c4::uncatsep() is the inverse of @ref
788  * c4::catsep(). */
789 template<class... Args>
790 substr catsep_sub(substr buf, Args && ...args)
791 {
792  size_t sz = catsep(buf, std::forward<Args>(args)...);
793  C4_CHECK(sz <= buf.len);
794  return {buf.str, sz <= buf.len ? sz : buf.len};
795 }
796 
797 /** @} */
798 
799 
800 
801 //-----------------------------------------------------------------------------
802 //-----------------------------------------------------------------------------
803 //-----------------------------------------------------------------------------
804 
805 /** @defgroup doc_uncatsep uncatsep: deserialize the separated arguments from a string
806  * @{ */
807 
808 /** @cond dev */
809 template<class Arg>
810 inline size_t uncatsep(csubstr buf, csubstr /*sep*/, Arg &C4_RESTRICT a)
811 {
812  return from_chars(buf, &a) ? buf.len : csubstr::npos;
813 }
814 /** @endcond */
815 
816 /** deserialize the arguments from the given buffer, using a separator.
817  *
818  * @return the number of characters read from the buffer, or csubstr::npos
819  * if a conversion was not successful
820  *
821  * @see c4::catsep(). @ref c4::uncatsep() is the inverse of @ref c4::catsep(). */
822 template<class Arg, class... Args>
823 size_t uncatsep(csubstr buf, csubstr sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
824 {
825  if(C4_LIKELY(sep.len > 0))
826  {
827  size_t pos = buf.find(sep);
828  if(C4_LIKELY(pos != csubstr::npos))
829  {
830  if(C4_LIKELY(from_chars(buf.first(pos), &a)))
831  {
832  pos += sep.len;
833  size_t num = uncatsep(buf.sub(pos), sep, more...);
834  if(C4_LIKELY(num != csubstr::npos))
835  return pos + num;
836  }
837  }
838  }
839  return csubstr::npos;
840 }
841 
842 /** @} */
843 
844 
845 //-----------------------------------------------------------------------------
846 //-----------------------------------------------------------------------------
847 //-----------------------------------------------------------------------------
848 
849 /** @defgroup doc_format format: formatted string interpolation
850  * @{ */
851 
852 /// @cond dev
853 // terminates the variadic recursion
854 inline size_t format(substr buf, csubstr fmt)
855 {
856  return to_chars(buf, fmt);
857 }
858 /// @endcond
859 
860 
861 /** Using a format string, serialize the arguments into the given
862  * fixed-size buffer. The buffer size is strictly respected: no writes
863  * will occur beyond its end. In the format string, each argument is
864  * marked with a compact curly-bracket pair "{}". This pair does not
865  * take any interior sequence numbers or extra formatting arguments
866  * inside it (contrary to eg the C++20 std::format implementation or
867  * the Python formatting facilities). To enable argument
868  * customization, use the formatting facilities in @ref
869  * doc_format_specifiers wrapping the arguments passed to this
870  * function.
871  *
872  * @return the number of bytes needed to write into the buffer.
873  *
874  * @see @ref c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired
875  * @see @ref c4::unformat() for the inverse function
876  * @see @ref c4::cat() if no format or separator is needed
877  * @see @ref c4::catsep() if no format is needed, but a separator must be used
878  *
879  * For example:
880  * @code{.cpp}
881  * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers
882  * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees
883  * @endcode
884  *
885  * Using @ref
886  * doc_format_specifiers enables control of the result. For example:
887  * @code{.cpp}
888  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::real(5, 3), "beers"); // the partier drank 5.000 beers
889  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::zpad(5, 3), "beers"); // the partier drank 005 beers
890  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::bin(5), "beers"); // the partier drank 0b101 beers
891  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::oct(5), "beers"); // the partier drank 0o6 beers
892  * c4::format(buf, "the {} drank {} {}", "partier", c4::fmt::hex(5), "beers"); // the partier drank 0x6 beers
893  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::real(6, 3), "coffees"); // the programmer drank 6.000 coffees
894  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::zpad(6, 3), "coffees"); // the programmer drank 006 coffees
895  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::bin(6), "coffees"); // the programmer drank 0b110 coffees
896  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::oct(6), "coffees"); // the programmer drank 0o6 coffees
897  * c4::format(buf, "the {} drank {} {}", "programmer", c4::fmt::hex(6), "coffees"); // the programmer drank 0x6 coffees
898  * @endcode
899  *
900  * @note Arguments beyond the last curly bracket pair are silently
901  * ignored. Curly bracket pairs without a corresponding argument are
902  * printed as part of the result.
903  * @code{.cpp}
904  * // note "and nothing else" being ignored
905  * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers", "and nothing else"); // the partier drank 5 beers
906  *
907  * // note "this is ignored {}" being part of the result
908  * c4::format(buf, "the {} drank {} {} this is ignored: {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees this is ignored: {}
909  * @endcode
910  *
911  * @note The curly bracket pair cannot be escaped, but can of course
912  * be placed into the result by passing an "{}" argument in its place,
913  * or if it is provided beyond the last argument passed to the
914  * function (see prior note).
915  * @code{.cpp}
916  * // as above: no argument given, so no substitution made:
917  * c4::format(buf, "let's show {} on the result"); // let's show {} on the result
918  * // or just pass "{}" as an argument to force the substitution:
919  * c4::format(buf, "let's show {} on the result and then {}", "{}", "this"); // let's show {} on the result and then this
920  * @endcode
921  *
922  * @note The arguments to format are restricted (legal because they
923  * are rvalues). This may require a workaround when arguments of type
924  * char[]/const char[] are passed repeatedly to the function. For
925  * example,
926  * @code{.cpp}
927  * const char str[] = "Hi! ";
928  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
929  * @endcode
930  * It is possible to work around the problem by suppressing -Wrestrict
931  * or by using the decayed type char* or const char*, or even wrapping
932  * the argument in a csubstr():
933  * @code{.cpp}
934  * const char str[] = "Hi! ";
935  * csubstr ss = to_csubstr(str);
936  * cat(buf, ss, ss, ss); // ok! compiles cleanly
937  * @endcode
938  */
939 template<class Arg, class... Args>
940 size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
941 {
942  size_t pos = fmt.find("{}");
943  if(C4_UNLIKELY(pos == csubstr::npos))
944  return to_chars(buf, fmt);
945  size_t num = to_chars(buf, fmt.first(pos));
946  size_t out = num;
947  buf = buf.len >= num ? buf.sub(num) : substr{};
948  num = to_chars(buf, a);
949  out += num;
950  buf = buf.len >= num ? buf.sub(num) : substr{};
951  num = format(buf, fmt.sub(pos + 2), more...);
952  out += num;
953  return out;
954 }
955 
956 /** like @ref c4::format() but return a substr instead of a size
957  * @see c4::format()
958  * @see c4::catsep(). @ref c4::uncatsep() is the inverse of @ref c4::catsep(). */
959 template<class... Args>
960 substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args)
961 {
962  size_t sz = c4::format(buf, fmt, args...);
963  C4_CHECK(sz <= buf.len);
964  return {buf.str, sz <= buf.len ? sz : buf.len};
965 }
966 
967 /** @} */
968 
969 
970 //-----------------------------------------------------------------------------
971 
972 /** @defgroup doc_unformat unformat: formatted read from string
973  * @{ */
974 
975 /// @cond dev
976 // terminates the variadic recursion
977 inline size_t unformat(csubstr /*buf*/, csubstr fmt)
978 {
979  return fmt.len;
980 }
981 /// @endcond
982 
983 
984 /** using a format string, deserialize the arguments from the given
985  * buffer. This is the inverse function to @ref c4::format().
986  *
987  * @return the number of characters read from the buffer, or npos if a conversion failed.
988  *
989  * @see @ref c4::format(). */
990 template<class Arg, class... Args>
991 size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
992 {
993  const size_t pos = fmt.find("{}");
994  if(C4_UNLIKELY(pos == csubstr::npos))
995  return unformat(buf, fmt);
996  size_t num = pos;
997  size_t out = num;
998  buf = buf.len >= num ? buf.sub(num) : substr{};
999  num = from_chars_first(buf, &a);
1000  if(C4_UNLIKELY(num == csubstr::npos))
1001  return csubstr::npos;
1002  out += num;
1003  buf = buf.len >= num ? buf.sub(num) : substr{};
1004  num = unformat(buf, fmt.sub(pos + 2), more...);
1005  if(C4_UNLIKELY(num == csubstr::npos))
1006  return csubstr::npos;
1007  out += num;
1008  return out;
1009 }
1010 
1011 /** @} */
1012 
1013 
1014 //-----------------------------------------------------------------------------
1015 //-----------------------------------------------------------------------------
1016 //-----------------------------------------------------------------------------
1017 
1018 /** cat+resize: like @ref c4::cat(), but receives a container, and
1019  * resizes it as needed to contain the result. The container is
1020  * overwritten. To append to it, use @ref c4::catrs_append().
1021  *
1022  * @see @ref c4::cat()
1023  * @see @ref c4::catrs_append()
1024  * @ingroup doc_cat
1025  *
1026  * @note The arguments to format are restricted (legal because they
1027  * are rvalues). This may require a workaround when arguments of type
1028  * char[]/const char[] are passed repeatedly to the function. For
1029  * example,
1030  * @code{.cpp}
1031  * const char str[] = "Hi! ";
1032  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1033  * @endcode
1034  * It is possible to work around the problem by suppressing -Wrestrict
1035  * or by using the decayed type char* or const char*, or even wrapping
1036  * the argument in a csubstr():
1037  * @code{.cpp}
1038  * const char str[] = "Hi! ";
1039  * csubstr ss = to_csubstr(str);
1040  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1041  * @endcode
1042  */
1043 template<class CharOwningContainer, class... Args>
1044 inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
1045 {
1046  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1047 retry:
1048  substr buf = to_substr(*cont);
1049  size_t ret = cat(buf, args...);
1050  cont->resize(ret);
1051  if(ret > buf.len)
1052  goto retry;
1053 }
1054 
1055 /** cat+resize: like @ref c4::cat(), but creates and returns a new
1056  * container sized as needed to contain the result.
1057  *
1058  * @see @ref c4::cat()
1059  * @ingroup doc_cat
1060  *
1061  * @note The arguments to format are restricted (legal because they
1062  * are rvalues). This may require a workaround when arguments of type
1063  * char[]/const char[] are passed repeatedly to the function. For
1064  * example,
1065  * @code{.cpp}
1066  * const char str[] = "Hi! ";
1067  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1068  * @endcode
1069  * It is possible to work around the problem by suppressing -Wrestrict
1070  * or by using the decayed type char* or const char*, or even wrapping
1071  * the argument in a csubstr():
1072  * @code{.cpp}
1073  * const char str[] = "Hi! ";
1074  * csubstr ss = to_csubstr(str);
1075  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1076  * @endcode
1077  */
1078 template<class CharOwningContainer, class... Args>
1079 inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args)
1080 {
1081  CharOwningContainer cont;
1082  catrs(&cont, args...);
1083  return cont;
1084 }
1085 
1086 /** cat+resize+append: like @ref c4::cat(), but receives a container,
1087  * and appends to it instead of overwriting it. The container is
1088  * resized as needed to contain the result.
1089  *
1090  * @return the region newly appended to the original container
1091  * @see @ref c4::cat()
1092  * @see @ref c4::catrs()
1093  * @ingroup doc_cat
1094  *
1095  * @note The arguments to format are restricted (legal because they
1096  * are rvalues). This may require a workaround when arguments of type
1097  * char[]/const char[] are passed repeatedly to the function. For
1098  * example,
1099  * @code{.cpp}
1100  * const char str[] = "Hi! ";
1101  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1102  * @endcode
1103  * It is possible to work around the problem by suppressing -Wrestrict
1104  * or by using the decayed type char* or const char*, or even wrapping
1105  * the argument in a csubstr():
1106  * @code{.cpp}
1107  * const char str[] = "Hi! ";
1108  * csubstr ss = to_csubstr(str);
1109  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1110  * @endcode
1111  */
1112 template<class CharOwningContainer, class... Args>
1113 inline csubstr catrs_append(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
1114 {
1115  const size_t pos = cont->size();
1116  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1117 retry:
1118  substr buf = to_substr(*cont).sub(pos);
1119  size_t ret = cat(buf, args...);
1120  cont->resize(pos + ret);
1121  if(ret > buf.len)
1122  goto retry;
1123  return to_csubstr(*cont).range(pos, cont->size());
1124 }
1125 
1126 
1127 //-----------------------------------------------------------------------------
1128 
1129 /** catsep+resize: like @ref c4::catsep(), but receives a container,
1130  * and resizes it as needed to contain the result. The container is
1131  * overwritten. To append to the container use @ref
1132  * c4::catseprs_append().
1133  *
1134  * @see @ref c4::catsep()
1135  * @see @ref c4::catseprs_append()
1136  * @ingroup doc_catsep
1137  *
1138  * @note The arguments to format are restricted (legal because they
1139  * are rvalues). This may require a workaround when arguments of type
1140  * char[]/const char[] are passed repeatedly to the function. For
1141  * example,
1142  * @code{.cpp}
1143  * const char str[] = "Hi! ";
1144  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1145  * @endcode
1146  * It is possible to work around the problem by suppressing -Wrestrict
1147  * or by using the decayed type char* or const char*, or even wrapping
1148  * the argument in a csubstr():
1149  * @code{.cpp}
1150  * const char str[] = "Hi! ";
1151  * csubstr ss = to_csubstr(str);
1152  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1153  * @endcode
1154  */
1155 template<class CharOwningContainer, class Sep, class... Args>
1156 inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
1157 {
1158  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1159 retry:
1160  substr buf = to_substr(*cont);
1161  size_t ret = catsep(buf, sep, args...);
1162  cont->resize(ret);
1163  if(ret > buf.len)
1164  goto retry;
1165 }
1166 
1167 /** catsep+resize: like @ref c4::catsep(), but create a new container
1168  * with the result.
1169  *
1170  * @return the requested container
1171  * @ingroup doc_catsep
1172  *
1173  * @note The arguments to format are restricted (legal because they
1174  * are rvalues). This may require a workaround when arguments of type
1175  * char[]/const char[] are passed repeatedly to the function. For
1176  * example,
1177  * @code{.cpp}
1178  * const char str[] = "Hi! ";
1179  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1180  * @endcode
1181  * It is possible to work around the problem by suppressing -Wrestrict
1182  * or by using the decayed type char* or const char*, or even wrapping
1183  * the argument in a csubstr():
1184  * @code{.cpp}
1185  * const char str[] = "Hi! ";
1186  * csubstr ss = to_csubstr(str);
1187  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1188  * @endcode
1189  */
1190 template<class CharOwningContainer, class Sep, class... Args>
1191 inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
1192 {
1193  CharOwningContainer cont;
1194  catseprs(&cont, sep, args...);
1195  return cont;
1196 }
1197 
1198 
1199 /** catsep+resize+append: like @ref c4::catsep(), but receives a
1200  * container, and appends the arguments, resizing the container as
1201  * needed to contain the result. The buffer is appended to.
1202  *
1203  * @return a csubstr of the appended part
1204  * @ingroup doc_catsep
1205  *
1206  * @note The arguments to format are restricted (legal because they
1207  * are rvalues). This may require a workaround when arguments of type
1208  * char[]/const char[] are passed repeatedly to the function. For
1209  * example,
1210  * @code{.cpp}
1211  * const char str[] = "Hi! ";
1212  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1213  * @endcode
1214  * It is possible to work around the problem by suppressing -Wrestrict
1215  * or by using the decayed type char* or const char*, or even wrapping
1216  * the argument in a csubstr():
1217  * @code{.cpp}
1218  * const char str[] = "Hi! ";
1219  * csubstr ss = to_csubstr(str);
1220  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1221  * @endcode
1222  */
1223 template<class CharOwningContainer, class Sep, class... Args>
1224 inline csubstr catseprs_append(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
1225 {
1226  const size_t pos = cont->size();
1227  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1228 retry:
1229  substr buf = to_substr(*cont).sub(pos);
1230  size_t ret = catsep(buf, sep, args...);
1231  cont->resize(pos + ret);
1232  if(ret > buf.len)
1233  goto retry;
1234  return to_csubstr(*cont).range(pos, cont->size());
1235 }
1236 
1237 
1238 //-----------------------------------------------------------------------------
1239 
1240 /** format+resize: like @ref c4::format(), but receives a container,
1241  * and resizes it as needed to contain the result. The container is
1242  * overwritten. To append to the container use @ref
1243  * c4::formatrs_append().
1244  *
1245  * @see @ref c4::format()
1246  * @see @ref c4::formatrs_append()
1247  * @ingroup doc_format
1248  *
1249  * @note The arguments to format are restricted (legal because they
1250  * are rvalues). This may require a workaround when arguments of type
1251  * char[]/const char[] are passed repeatedly to the function. For
1252  * example,
1253  * @code{.cpp}
1254  * const char str[] = "Hi! ";
1255  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1256  * @endcode
1257  * It is possible to work around the problem by suppressing -Wrestrict
1258  * or by using the decayed type char* or const char*, or even wrapping
1259  * the argument in a csubstr():
1260  * @code{.cpp}
1261  * const char str[] = "Hi! ";
1262  * csubstr ss = to_csubstr(str);
1263  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1264  * @endcode
1265  */
1266 template<class CharOwningContainer, class... Args>
1267 inline void formatrs(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
1268 {
1269  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1270 retry:
1271  substr buf = to_substr(*cont);
1272  size_t ret = format(buf, fmt, args...);
1273  cont->resize(ret);
1274  if(ret > buf.len)
1275  goto retry;
1276 }
1277 
1278 /** format+resize: like @ref c4::format(), but create a new container
1279  * with the result.
1280  *
1281  * @return the requested container
1282  * @ingroup doc_format
1283  *
1284  * @note The arguments to format are restricted (legal because they
1285  * are rvalues). This may require a workaround when arguments of type
1286  * char[]/const char[] are passed repeatedly to the function. For
1287  * example,
1288  * @code{.cpp}
1289  * const char str[] = "Hi! ";
1290  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1291  * @endcode
1292  * It is possible to work around the problem by suppressing -Wrestrict
1293  * or by using the decayed type char* or const char*, or even wrapping
1294  * the argument in a csubstr():
1295  * @code{.cpp}
1296  * const char str[] = "Hi! ";
1297  * csubstr ss = to_csubstr(str);
1298  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1299  * @endcode
1300  */
1301 template<class CharOwningContainer, class... Args>
1302 inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args)
1303 {
1304  CharOwningContainer cont;
1305  formatrs(&cont, fmt, args...);
1306  return cont;
1307 }
1308 
1309 /** format+resize+append: like @ref c4::format(), but receives a
1310  * container, and appends the arguments, resizing the container as
1311  * needed to contain the result. The buffer is appended to.
1312  *
1313  * @return the region newly appended to the original container
1314  * @ingroup doc_format
1315  *
1316  * @note The arguments to format are restricted (legal because they
1317  * are rvalues). This may require a workaround when arguments of type
1318  * char[]/const char[] are passed repeatedly to the function. For
1319  * example,
1320  * @code{.cpp}
1321  * const char str[] = "Hi! ";
1322  * cat(buf, str, str, str); // compile error: 'passing argument 2 to ‘restrict’-qualified parameter aliases with arguments 3, 4'
1323  * @endcode
1324  * It is possible to work around the problem by suppressing -Wrestrict
1325  * or by using the decayed type char* or const char*, or even wrapping
1326  * the argument in a csubstr():
1327  * @code{.cpp}
1328  * const char str[] = "Hi! ";
1329  * csubstr ss = to_csubstr(str);
1330  * cat(buf, ss, ss, ss); // ok! compiles cleanly
1331  * @endcode
1332  */
1333 template<class CharOwningContainer, class... Args>
1334 inline csubstr formatrs_append(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
1335 {
1336  const size_t pos = cont->size();
1337  cont->resize(cont->capacity()); // improve the odds of fitting in the original buffer
1338 retry:
1339  substr buf = to_substr(*cont).sub(pos);
1340  size_t ret = format(buf, fmt, args...);
1341  cont->resize(pos + ret);
1342  if(ret > buf.len)
1343  goto retry;
1344  return to_csubstr(*cont).range(pos, cont->size());
1345 }
1346 
1347 /** @} */
1348 
1349 } // namespace c4
1350 
1351 // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast,*avoid-goto*)
1352 #ifdef _MSC_VER
1353 # pragma warning(pop)
1354 #elif defined(__clang__)
1355 # pragma clang diagnostic pop
1356 #elif defined(__GNUC__)
1357 # pragma GCC diagnostic pop
1358 #endif
1359 
1360 #endif /* _C4_FORMAT_HPP_ */
Lightweight generic type-safe wrappers for converting individual values to/from strings.
center_< T > center(T val, size_t width, char padchar=' ')
tag function to mark an argument to be aligned center
Definition: format.hpp:538
left_< T > left(T val, size_t width, char padchar=' ')
tag type to mark an argument to be aligned left.
Definition: format.hpp:519
right_< T > right(T val, size_t width, char padchar=' ')
tag function to mark an argument to be aligned right
Definition: format.hpp:557
bool atox(csubstr s, uint8_t *v) noexcept
Definition: charconv.hpp:2287
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
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:1113
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:653
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:1044
substr cat_sub(substr buf, Args const &...args)
like c4::cat() but return a substr instead of a size
Definition: format.hpp:663
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:1224
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:1156
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:778
substr catsep_sub(substr buf, Args &&...args)
like c4::catsep() but return a substr instead of a size
Definition: format.hpp:790
RealFormat_e
Definition: charconv.hpp:192
@ FTOA_FLOAT
print the real number in floating point format (like f)
Definition: charconv.hpp:194
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:2082
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:1334
substr format_sub(substr buf, csubstr fmt, Args const &...args)
like c4::format() but return a substr instead of a size
Definition: format.hpp:960
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:940
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:1267
size_t from_chars_first(csubstr buf, uint8_t *v) noexcept
Definition: charconv.hpp:2396
bool from_chars(csubstr buf, uint8_t *v) noexcept
Definition: charconv.hpp:2368
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:2056
integral_< intptr_t > hex(std::nullptr_t)
format null as an hexadecimal value
Definition: format.hpp:160
integral_< T > integral(T val, T radix=10)
format an integral type with a custom radix
Definition: format.hpp:128
integral_< intptr_t > bin(std::nullptr_t)
format null as a binary 0-1 value
Definition: format.hpp:212
integral_< intptr_t > oct(std::nullptr_t)
format null as an octal value
Definition: format.hpp:185
size_t itoa(substr buf, T v) noexcept
convert an integral signed decimal to a string.
Definition: charconv.hpp:1107
overflow_checked_< T > overflow_checked(T &val)
Definition: format.hpp:273
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:422
raw_wrapper_< byte > raw_wrapper
Definition: format.hpp:412
raw_wrapper_< cbyte > const_raw_wrapper
Definition: format.hpp:411
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:416
real_< T > real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
Definition: format.hpp:366
csubstr to_csubstr(substr s) noexcept
neutral version for use in generic code
Definition: substr.hpp:2204
substr to_substr(substr s) noexcept
neutral version for use in generic code
Definition: substr.hpp:2202
size_t to_chars(substr buf, uint8_t v) noexcept
Definition: charconv.hpp:2327
size_t uncat(csubstr buf, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:694
size_t uncatsep(csubstr buf, csubstr sep, Arg &a, Args &...more)
deserialize the arguments from the given buffer, using a separator.
Definition: format.hpp:823
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:991
size_t utoa(substr buf, T v) noexcept
convert an integral unsigned decimal to a string.
Definition: charconv.hpp:1302
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
@ npos
a null string position
Definition: common.hpp:258
(Undefined by default) Use shorter error message from checks/asserts: do not show the check condition...
Definition: common.cpp:14
center_(T v, size_t w, char c) noexcept
Definition: format.hpp:502
format an integral type with a custom radix
Definition: format.hpp:107
C4_STATIC_ASSERT(std::is_integral< T >::value)
integral_(T val_, T radix_)
Definition: format.hpp:111
format an integral type with a custom radix, and pad with zeroes on the left
Definition: format.hpp:117
integral_padded_(T val_, T radix_, size_t nd)
Definition: format.hpp:122
C4_STATIC_ASSERT(std::is_integral< T >::value)
size_t width
Definition: format.hpp:501
left_(T v, size_t w, char c) noexcept
Definition: format.hpp:501
raw_wrapper_(blob_< T > data, size_t alignment_) noexcept
Definition: format.hpp:402
RealFormat_e fmt
Definition: format.hpp:361
real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT)
Definition: format.hpp:362
right_(T v, size_t w, char c) noexcept
Definition: format.hpp:503
size_t width
Definition: format.hpp:503