rapidyaml 0.14.0
parse and emit YAML, and do it fast
Loading...
Searching...
No Matches
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
44namespace c4 {
45
46/** @addtogroup doc_format_utils
47 * @{ */
48
49//-----------------------------------------------------------------------------
50//-----------------------------------------------------------------------------
51//-----------------------------------------------------------------------------
52// formatting truthy types as booleans
53
54namespace fmt {
55
56/** @addtogroup doc_format_specifiers
57 * @{ */
58
59/** @defgroup doc_boolean_specifiers boolean specifiers
60 * @{ */
61
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 */
69template<class T>
70boolalpha_ 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 */
85{
86 return to_chars(buf, fmt.val ? "true" : "false");
87}
88
89
90
91//-----------------------------------------------------------------------------
92//-----------------------------------------------------------------------------
93//-----------------------------------------------------------------------------
94// formatting integral types
95
96namespace 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 */
105template<typename T>
107{
108 C4_STATIC_ASSERT(std::is_integral<T>::value);
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 */
115template<typename T>
117{
118 C4_STATIC_ASSERT(std::is_integral<T>::value);
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 */
127template<class T>
128C4_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 */
133template<class T>
134C4_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 */
139template<class T>
140C4_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 */
147template<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 */
153template<class T>
154inline 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 */
160inline 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 */
166template<class T>
167inline integral_<T> hex(T v)
168{
169 return integral_<T>(v, T(16));
170}
171
172/** format the pointer as an octal value */
173template<class T>
174inline 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 */
179template<class T>
181{
182 return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
183}
184/** format null as an octal value */
185inline 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 */
190template<class T>
191inline 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 */
198template<class T>
199inline 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 */
205template<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 */
212inline 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 */
218template<class T>
219inline 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 */
231template<class T>
232C4_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 */
237template<class T>
238C4_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 */
243C4_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 */
248template<class T>
249C4_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}
253template<class T>
254C4_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
265template<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};
272template<class T>
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 */
287template<typename T>
288C4_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 */
295template<typename T>
296C4_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 */
304template<typename T>
305C4_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 */
312template<typename T>
313C4_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 */
321template<class T>
322C4_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 */
330template<class T>
331C4_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
344namespace fmt {
345
346/** @addtogroup doc_format_specifiers
347 * @{ */
348
349/** @defgroup doc_real_specifiers Real specifiers
350 * @{ */
351
352template<class T>
353struct real_
354{
358 real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {}
359};
360
361template<class T>
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 */
374inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); }
375/** @ingroup doc_to_chars */
376inline 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
384namespace fmt {
385
386/** @addtogroup doc_format_specifiers
387 * @{ */
388
389/** @defgroup doc_raw_binary_specifiers Raw binary data
390 * @{ */
391
392/** @see blob_ */
393template<class T>
394struct 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_ */
412inline 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_ */
418inline 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_ */
424template<class T>
425inline 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_ */
431template<class T>
432inline 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 */
438inline 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 */
443template<class T>
444inline 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 */
458C4CORE_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 */
462C4CORE_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
489namespace fmt {
490
491/** @addtogroup doc_format_specifiers
492 * @{ */
493
494/** @defgroup doc_alignment_specifiers Alignment specifiers
495 * @{ */
496
497template<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) {} };
498template<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) {} };
499template<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 */
514template<class T>
515left_<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 */
533template<class T>
534center_<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 */
552template<class T>
553right_<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 */
566template<class T>
567size_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 */
577template<class T>
578size_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 */
591template<class T>
592size_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
616inline 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 */
648template<class Arg, class... Args>
649size_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 */
658template<class... Args>
659substr 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
677inline 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(). */
689template<class Arg, class... Args>
690size_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 */
715namespace detail {
716template<class Sep>
717C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
718{
719 return 0;
720}
721
722template<class Sep, class Arg, class... Args>
723size_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
738template<class Sep>
739size_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 */
773template<class Sep, class Arg, class... Args>
774size_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(). */
785template<class... Args>
786substr 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 */
805template<class Arg>
806inline 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(). */
818template<class Arg, class... Args>
819size_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
850inline 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 */
935template<class Arg, class... Args>
936size_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(). */
955template<class... Args>
956substr 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
973inline 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(). */
986template<class Arg, class... Args>
987size_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 */
1039template<class CharOwningContainer, class... Args>
1040inline 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
1043retry:
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 */
1074template<class CharOwningContainer, class... Args>
1075inline 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 */
1108template<class CharOwningContainer, class... Args>
1109inline 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
1113retry:
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 */
1151template<class CharOwningContainer, class Sep, class... Args>
1152inline 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
1155retry:
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 */
1186template<class CharOwningContainer, class Sep, class... Args>
1187inline 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 */
1219template<class CharOwningContainer, class Sep, class... Args>
1220inline 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
1224retry:
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 */
1262template<class CharOwningContainer, class... Args>
1263inline 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
1266retry:
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 */
1297template<class CharOwningContainer, class... Args>
1298inline 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 */
1329template<class CharOwningContainer, class... Args>
1330inline 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
1336retry:
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_ */
Lightweight generic type-safe wrappers for converting individual values to/from strings.
right_< T > right(T val, size_t width, char padchar=' ')
tag function to mark an argument to be aligned right
Definition format.hpp:553
center_< T > center(T val, size_t width, char padchar=' ')
tag function to mark an argument to be aligned center
Definition format.hpp:534
left_< T > left(T val, size_t width, char padchar=' ')
tag type to mark an argument to be aligned left.
Definition format.hpp:515
bool atox(csubstr s, uint8_t *v) noexcept
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:1109
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
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
substr cat_sub(substr buf, Args const &...args)
like c4::cat() but return a substr instead of a size
Definition format.hpp:659
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
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 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
substr catsep_sub(substr buf, Args &&...args)
like c4::catsep() but return a substr instead of a size
Definition format.hpp:786
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.
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
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
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
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
size_t from_chars_first(csubstr buf, uint8_t *v) noexcept
bool from_chars(csubstr buf, uint8_t *v) noexcept
size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
Convert a single-precision real number to string.
integral_< intptr_t > hex(std::nullptr_t)
format null as an hexadecimal value
Definition format.hpp:160
integral_< intptr_t > oct(std::nullptr_t)
format null as an octal value
Definition format.hpp:185
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
size_t itoa(substr buf, T v) noexcept
convert an integral signed decimal to a string.
overflow_checked_< T > overflow_checked(T &val)
Definition format.hpp:273
auto overflows(csubstr str) noexcept -> typename std::enable_if< std::is_unsigned< T >::value, bool >::type
Test if the following string would overflow when converted to associated integral types; this functio...
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_< cbyte > const_raw_wrapper
Definition format.hpp:407
raw_wrapper_< byte > raw_wrapper
Definition format.hpp:408
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
real_< T > real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
Definition format.hpp:362
size_t to_chars(ryml::substr buf, vec2< T > v)
substr to_substr(char(&s)[N]) noexcept
Definition substr.hpp:2377
csubstr to_csubstr(const char(&s)[N]) noexcept
Definition substr.hpp:2381
basic_substring< char > substr
a mutable string view
Definition substr.hpp:2356
basic_substring< const char > csubstr
an immutable string view
Definition substr.hpp:2357
size_t to_chars(substr buf, uint8_t v) noexcept
size_t uncat(csubstr buf, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition format.hpp:690
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
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
size_t utoa(substr buf, T v) noexcept
convert an integral unsigned decimal to a string.
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
(Undefined by default) Use shorter error message from checks/asserts: do not show the check condition...
Definition common.cpp:14
basic_substring range(size_t first, size_t last=npos) const noexcept
return [first,last[.
Definition substr.hpp:520
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:2154
size_t len
the length of the substring
Definition substr.hpp:218
size_t find(const C c, size_t start_pos=0) const
Definition substr.hpp:714
size_t size() const noexcept
Definition substr.hpp:358
basic_substring first(size_t num) const noexcept
return the first num elements: [0,num[
Definition substr.hpp:530
basic_substring sub(size_t first) const noexcept
return [first,len[
Definition substr.hpp:503
C * str
a restricted pointer to the first character of the substring
Definition substr.hpp:216
center_(T v, size_t w, char c) noexcept
Definition format.hpp:498
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)
left_(T v, size_t w, char c) noexcept
Definition format.hpp:497
raw_wrapper_(blob_< T > data, size_t alignment_) noexcept
Definition format.hpp:398
RealFormat_e fmt
Definition format.hpp:357
real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT)
Definition format.hpp:358
right_(T v, size_t w, char c) noexcept
Definition format.hpp:499