duckstation

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

format.hpp (27929B)


      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 #ifdef _MSC_VER
     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 
     24 namespace c4 {
     25 
     26 
     27 //-----------------------------------------------------------------------------
     28 //-----------------------------------------------------------------------------
     29 //-----------------------------------------------------------------------------
     30 // formatting truthy types as booleans
     31 
     32 namespace fmt {
     33 
     34 /** write a variable as an alphabetic boolean, ie as either true or false
     35  * @param strict_read */
     36 template<class T>
     37 struct boolalpha_
     38 {
     39     boolalpha_(T val_, bool strict_read_=false) : val(val_ ? true : false), strict_read(strict_read_) {}
     40     bool val;
     41     bool strict_read;
     42 };
     43 
     44 template<class T>
     45 boolalpha_<T> boolalpha(T const& val, bool strict_read=false)
     46 {
     47     return boolalpha_<T>(val, strict_read);
     48 }
     49 
     50 } // namespace fmt
     51 
     52 /** write a variable as an alphabetic boolean, ie as either true or false */
     53 template<class T>
     54 inline size_t to_chars(substr buf, fmt::boolalpha_<T> fmt)
     55 {
     56     return to_chars(buf, fmt.val ? "true" : "false");
     57 }
     58 
     59 
     60 
     61 //-----------------------------------------------------------------------------
     62 //-----------------------------------------------------------------------------
     63 //-----------------------------------------------------------------------------
     64 // formatting integral types
     65 
     66 namespace fmt {
     67 
     68 /** format an integral type with a custom radix */
     69 template<typename T>
     70 struct integral_
     71 {
     72     T val;
     73     T radix;
     74     C4_ALWAYS_INLINE integral_(T val_, T radix_) : val(val_), radix(radix_) {}
     75 };
     76 
     77 /** format an integral type with a custom radix, and pad with zeroes on the left */
     78 template<typename T>
     79 struct integral_padded_
     80 {
     81     T val;
     82     T radix;
     83     size_t num_digits;
     84     C4_ALWAYS_INLINE integral_padded_(T val_, T radix_, size_t nd) : val(val_), radix(radix_), num_digits(nd) {}
     85 };
     86 
     87 /** format an integral type with a custom radix */
     88 template<class T>
     89 C4_ALWAYS_INLINE integral_<T> integral(T val, T radix=10)
     90 {
     91     return integral_<T>(val, radix);
     92 }
     93 /** format an integral type with a custom radix */
     94 template<class T>
     95 C4_ALWAYS_INLINE integral_<intptr_t> integral(T const* val, T radix=10)
     96 {
     97     return integral_<intptr_t>(reinterpret_cast<intptr_t>(val), static_cast<intptr_t>(radix));
     98 }
     99 /** format an integral type with a custom radix */
    100 template<class T>
    101 C4_ALWAYS_INLINE integral_<intptr_t> integral(std::nullptr_t, T radix=10)
    102 {
    103     return integral_<intptr_t>(intptr_t(0), static_cast<intptr_t>(radix));
    104 }
    105 /** pad the argument with zeroes on the left, with decimal radix */
    106 template<class T>
    107 C4_ALWAYS_INLINE integral_padded_<T> zpad(T val, size_t num_digits)
    108 {
    109     return integral_padded_<T>(val, T(10), num_digits);
    110 }
    111 /** pad the argument with zeroes on the left */
    112 template<class T>
    113 C4_ALWAYS_INLINE integral_padded_<T> zpad(integral_<T> val, size_t num_digits)
    114 {
    115     return integral_padded_<T>(val.val, val.radix, num_digits);
    116 }
    117 /** pad the argument with zeroes on the left */
    118 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(std::nullptr_t, size_t num_digits)
    119 {
    120     return integral_padded_<intptr_t>(0, 16, num_digits);
    121 }
    122 /** pad the argument with zeroes on the left */
    123 template<class T>
    124 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T const* val, size_t num_digits)
    125 {
    126     return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits);
    127 }
    128 template<class T>
    129 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T * val, size_t num_digits)
    130 {
    131     return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits);
    132 }
    133 
    134 
    135 /** format the pointer as an hexadecimal value */
    136 template<class T>
    137 inline integral_<intptr_t> hex(T * v)
    138 {
    139     return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16));
    140 }
    141 /** format the pointer as an hexadecimal value */
    142 template<class T>
    143 inline integral_<intptr_t> hex(T const* v)
    144 {
    145     return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16));
    146 }
    147 /** format null as an hexadecimal value
    148  * @overload hex */
    149 inline integral_<intptr_t> hex(std::nullptr_t)
    150 {
    151     return integral_<intptr_t>(0, intptr_t(16));
    152 }
    153 /** format the integral_ argument as an hexadecimal value
    154  * @overload hex */
    155 template<class T>
    156 inline integral_<T> hex(T v)
    157 {
    158     return integral_<T>(v, T(16));
    159 }
    160 
    161 /** format the pointer as an octal value */
    162 template<class T>
    163 inline integral_<intptr_t> oct(T const* v)
    164 {
    165     return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
    166 }
    167 /** format the pointer as an octal value */
    168 template<class T>
    169 inline integral_<intptr_t> oct(T * v)
    170 {
    171     return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
    172 }
    173 /** format null as an octal value */
    174 inline integral_<intptr_t> oct(std::nullptr_t)
    175 {
    176     return integral_<intptr_t>(intptr_t(0), intptr_t(8));
    177 }
    178 /** format the integral_ argument as an octal value */
    179 template<class T>
    180 inline integral_<T> oct(T v)
    181 {
    182     return integral_<T>(v, T(8));
    183 }
    184 
    185 /** format the pointer as a binary 0-1 value
    186  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
    187 template<class T>
    188 inline integral_<intptr_t> bin(T const* v)
    189 {
    190     return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(2));
    191 }
    192 /** format the pointer as a binary 0-1 value
    193  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
    194 template<class T>
    195 inline integral_<intptr_t> bin(T * v)
    196 {
    197     return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(2));
    198 }
    199 /** format null as a binary 0-1 value
    200  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
    201 inline integral_<intptr_t> bin(std::nullptr_t)
    202 {
    203     return integral_<intptr_t>(intptr_t(0), intptr_t(2));
    204 }
    205 /** format the integral_ argument as a binary 0-1 value
    206  * @see c4::raw() if you want to use a raw memcpy-based binary dump instead of 0-1 formatting */
    207 template<class T>
    208 inline integral_<T> bin(T v)
    209 {
    210     return integral_<T>(v, T(2));
    211 }
    212 
    213 
    214 template<class T>
    215 struct overflow_checked_
    216 {
    217     static_assert(std::is_integral<T>::value, "range checking only for integral types");
    218     C4_ALWAYS_INLINE overflow_checked_(T &val_) : val(&val_) {}
    219     T *val;
    220 };
    221 template<class T>
    222 C4_ALWAYS_INLINE overflow_checked_<T> overflow_checked(T &val)
    223 {
    224    return overflow_checked_<T>(val);
    225 }
    226 
    227 } // namespace fmt
    228 
    229 /** format an integral_ signed type */
    230 template<typename T>
    231 C4_ALWAYS_INLINE
    232 typename std::enable_if<std::is_signed<T>::value, size_t>::type
    233 to_chars(substr buf, fmt::integral_<T> fmt)
    234 {
    235     return itoa(buf, fmt.val, fmt.radix);
    236 }
    237 /** format an integral_ signed type, pad with zeroes */
    238 template<typename T>
    239 C4_ALWAYS_INLINE
    240 typename std::enable_if<std::is_signed<T>::value, size_t>::type
    241 to_chars(substr buf, fmt::integral_padded_<T> fmt)
    242 {
    243     return itoa(buf, fmt.val, fmt.radix, fmt.num_digits);
    244 }
    245 
    246 /** format an integral_ unsigned type */
    247 template<typename T>
    248 C4_ALWAYS_INLINE
    249 typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
    250 to_chars(substr buf, fmt::integral_<T> fmt)
    251 {
    252     return utoa(buf, fmt.val, fmt.radix);
    253 }
    254 /** format an integral_ unsigned type, pad with zeroes */
    255 template<typename T>
    256 C4_ALWAYS_INLINE
    257 typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
    258 to_chars(substr buf, fmt::integral_padded_<T> fmt)
    259 {
    260     return utoa(buf, fmt.val, fmt.radix, fmt.num_digits);
    261 }
    262 
    263 template<class T>
    264 C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_<T> wrapper)
    265 {
    266     if(C4_LIKELY(!overflows<T>(s)))
    267         return atox(s, wrapper.val);
    268     return false;
    269 }
    270 
    271 
    272 //-----------------------------------------------------------------------------
    273 //-----------------------------------------------------------------------------
    274 //-----------------------------------------------------------------------------
    275 // formatting real types
    276 
    277 namespace fmt {
    278 
    279 template<class T>
    280 struct real_
    281 {
    282     T val;
    283     int precision;
    284     RealFormat_e fmt;
    285     real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f)  {}
    286 };
    287 
    288 template<class T>
    289 real_<T> real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
    290 {
    291     return real_<T>(val, precision, fmt);
    292 }
    293 
    294 } // namespace fmt
    295 
    296 inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); }
    297 inline size_t to_chars(substr buf, fmt::real_<double> fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); }
    298 
    299 
    300 //-----------------------------------------------------------------------------
    301 //-----------------------------------------------------------------------------
    302 //-----------------------------------------------------------------------------
    303 // writing raw binary data
    304 
    305 namespace fmt {
    306 
    307 /** @see blob_ */
    308 template<class T>
    309 struct raw_wrapper_ : public blob_<T>
    310 {
    311     size_t alignment;
    312 
    313     C4_ALWAYS_INLINE raw_wrapper_(blob_<T> data, size_t alignment_) noexcept
    314         :
    315         blob_<T>(data),
    316         alignment(alignment_)
    317     {
    318         C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two");
    319     }
    320 };
    321 
    322 using const_raw_wrapper = raw_wrapper_<cbyte>;
    323 using raw_wrapper = raw_wrapper_<byte>;
    324 
    325 /** mark a variable to be written in raw binary format, using memcpy
    326  * @see blob_ */
    327 inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
    328 {
    329     return const_raw_wrapper(data, alignment);
    330 }
    331 /** mark a variable to be written in raw binary format, using memcpy
    332  * @see blob_ */
    333 inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
    334 {
    335     return const_raw_wrapper(data, alignment);
    336 }
    337 /** mark a variable to be written in raw binary format, using memcpy
    338  * @see blob_ */
    339 template<class T>
    340 inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
    341 {
    342     return const_raw_wrapper(cblob(data), alignment);
    343 }
    344 /** mark a variable to be written in raw binary format, using memcpy
    345  * @see blob_ */
    346 template<class T>
    347 inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
    348 {
    349     return const_raw_wrapper(cblob(data), alignment);
    350 }
    351 
    352 /** mark a variable to be read in raw binary format, using memcpy */
    353 inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t))
    354 {
    355     return raw_wrapper(data, alignment);
    356 }
    357 /** mark a variable to be read in raw binary format, using memcpy */
    358 template<class T>
    359 inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T))
    360 {
    361     return raw_wrapper(blob(data), alignment);
    362 }
    363 
    364 } // namespace fmt
    365 
    366 
    367 /** write a variable in raw binary format, using memcpy */
    368 C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r);
    369 
    370 /** read a variable in raw binary format, using memcpy */
    371 C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r);
    372 /** read a variable in raw binary format, using memcpy */
    373 inline bool from_chars(csubstr buf, fmt::raw_wrapper r)
    374 {
    375     return from_chars(buf, &r);
    376 }
    377 
    378 /** read a variable in raw binary format, using memcpy */
    379 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper *r)
    380 {
    381     return from_chars(buf, r);
    382 }
    383 /** read a variable in raw binary format, using memcpy */
    384 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r)
    385 {
    386     return from_chars(buf, &r);
    387 }
    388 
    389 
    390 //-----------------------------------------------------------------------------
    391 //-----------------------------------------------------------------------------
    392 //-----------------------------------------------------------------------------
    393 // formatting aligned to left/right
    394 
    395 namespace fmt {
    396 
    397 template<class T>
    398 struct left_
    399 {
    400     T val;
    401     size_t width;
    402     char pad;
    403     left_(T v, size_t w, char p) : val(v), width(w), pad(p) {}
    404 };
    405 
    406 template<class T>
    407 struct right_
    408 {
    409     T val;
    410     size_t width;
    411     char pad;
    412     right_(T v, size_t w, char p) : val(v), width(w), pad(p) {}
    413 };
    414 
    415 /** mark an argument to be aligned left */
    416 template<class T>
    417 left_<T> left(T val, size_t width, char padchar=' ')
    418 {
    419     return left_<T>(val, width, padchar);
    420 }
    421 
    422 /** mark an argument to be aligned right */
    423 template<class T>
    424 right_<T> right(T val, size_t width, char padchar=' ')
    425 {
    426     return right_<T>(val, width, padchar);
    427 }
    428 
    429 } // namespace fmt
    430 
    431 
    432 template<class T>
    433 size_t to_chars(substr buf, fmt::left_<T> const& C4_RESTRICT align)
    434 {
    435     size_t ret = to_chars(buf, align.val);
    436     if(ret >= buf.len || ret >= align.width)
    437         return ret > align.width ? ret : align.width;
    438     buf.first(align.width).sub(ret).fill(align.pad);
    439     to_chars(buf, align.val);
    440     return align.width;
    441 }
    442 
    443 template<class T>
    444 size_t to_chars(substr buf, fmt::right_<T> const& C4_RESTRICT align)
    445 {
    446     size_t ret = to_chars(buf, align.val);
    447     if(ret >= buf.len || ret >= align.width)
    448         return ret > align.width ? ret : align.width;
    449     size_t rem = static_cast<size_t>(align.width - ret);
    450     buf.first(rem).fill(align.pad);
    451     to_chars(buf.sub(rem), align.val);
    452     return align.width;
    453 }
    454 
    455 
    456 //-----------------------------------------------------------------------------
    457 //-----------------------------------------------------------------------------
    458 //-----------------------------------------------------------------------------
    459 
    460 /// @cond dev
    461 // terminates the variadic recursion
    462 inline size_t cat(substr /*buf*/)
    463 {
    464     return 0;
    465 }
    466 /// @endcond
    467 
    468 
    469 /** serialize the arguments, concatenating them to the given fixed-size buffer.
    470  * The buffer size is strictly respected: no writes will occur beyond its end.
    471  * @return the number of characters needed to write all the arguments into the buffer.
    472  * @see c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired
    473  * @see c4::uncat() for the inverse function
    474  * @see c4::catsep() if a separator between each argument is to be used
    475  * @see c4::format() if a format string is desired */
    476 template<class Arg, class... Args>
    477 size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
    478 {
    479     size_t num = to_chars(buf, a);
    480     buf  = buf.len >= num ? buf.sub(num) : substr{};
    481     num += cat(buf, more...);
    482     return num;
    483 }
    484 
    485 /** like c4::cat() but return a substr instead of a size */
    486 template<class... Args>
    487 substr cat_sub(substr buf, Args && ...args)
    488 {
    489     size_t sz = cat(buf, std::forward<Args>(args)...);
    490     C4_CHECK(sz <= buf.len);
    491     return {buf.str, sz <= buf.len ? sz : buf.len};
    492 }
    493 
    494 
    495 //-----------------------------------------------------------------------------
    496 
    497 /// @cond dev
    498 // terminates the variadic recursion
    499 inline size_t uncat(csubstr /*buf*/)
    500 {
    501     return 0;
    502 }
    503 /// @endcond
    504 
    505 
    506 /** deserialize the arguments from the given buffer.
    507  *
    508  * @return the number of characters read from the buffer, or csubstr::npos
    509  *   if a conversion was not successful.
    510  * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */
    511 template<class Arg, class... Args>
    512 size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
    513 {
    514     size_t out = from_chars_first(buf, &a);
    515     if(C4_UNLIKELY(out == csubstr::npos))
    516         return csubstr::npos;
    517     buf  = buf.len >= out ? buf.sub(out) : substr{};
    518     size_t num = uncat(buf, more...);
    519     if(C4_UNLIKELY(num == csubstr::npos))
    520         return csubstr::npos;
    521     return out + num;
    522 }
    523 
    524 
    525 
    526 //-----------------------------------------------------------------------------
    527 //-----------------------------------------------------------------------------
    528 //-----------------------------------------------------------------------------
    529 
    530 namespace detail {
    531 
    532 template<class Sep>
    533 C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
    534 {
    535     return 0;
    536 }
    537 
    538 template<class Sep, class Arg, class... Args>
    539 size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
    540 {
    541     size_t ret = to_chars(buf, sep);
    542     size_t num = ret;
    543     buf  = buf.len >= ret ? buf.sub(ret) : substr{};
    544     ret  = to_chars(buf, a);
    545     num += ret;
    546     buf  = buf.len >= ret ? buf.sub(ret) : substr{};
    547     ret  = catsep_more(buf, sep, more...);
    548     num += ret;
    549     return num;
    550 }
    551 
    552 
    553 template<class Sep>
    554 inline size_t uncatsep_more(csubstr /*buf*/, Sep & /*sep*/)
    555 {
    556     return 0;
    557 }
    558 
    559 template<class Sep, class Arg, class... Args>
    560 size_t uncatsep_more(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
    561 {
    562     size_t ret = from_chars_first(buf, &sep);
    563     size_t num = ret;
    564     if(C4_UNLIKELY(ret == csubstr::npos))
    565         return csubstr::npos;
    566     buf  = buf.len >= ret ? buf.sub(ret) : substr{};
    567     ret  = from_chars_first(buf, &a);
    568     if(C4_UNLIKELY(ret == csubstr::npos))
    569         return csubstr::npos;
    570     num += ret;
    571     buf  = buf.len >= ret ? buf.sub(ret) : substr{};
    572     ret  = uncatsep_more(buf, sep, more...);
    573     if(C4_UNLIKELY(ret == csubstr::npos))
    574         return csubstr::npos;
    575     num += ret;
    576     return num;
    577 }
    578 
    579 } // namespace detail
    580 
    581 /// @cond dev
    582 template<class Sep>
    583 size_t catsep(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
    584 {
    585     return 0;
    586 }
    587 /// @endcond
    588 
    589 /** serialize the arguments, concatenating them to the given fixed-size
    590  * buffer, using a separator between each argument.
    591  * The buffer size is strictly respected: no writes will occur beyond its end.
    592  * @return the number of characters needed to write all the arguments into the buffer.
    593  * @see c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired
    594  * @see c4::uncatsep() for the inverse function (ie, reading instead of writing)
    595  * @see c4::cat() if no separator is needed
    596  * @see c4::format() if a format string is desired */
    597 template<class Sep, class Arg, class... Args>
    598 size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
    599 {
    600     size_t num = to_chars(buf, a);
    601     buf  = buf.len >= num ? buf.sub(num) : substr{};
    602     num += detail::catsep_more(buf, sep, more...);
    603     return num;
    604 }
    605 
    606 /** like c4::catsep() but return a substr instead of a size
    607  * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */
    608 template<class... Args>
    609 substr catsep_sub(substr buf, Args && ...args)
    610 {
    611     size_t sz = catsep(buf, std::forward<Args>(args)...);
    612     C4_CHECK(sz <= buf.len);
    613     return {buf.str, sz <= buf.len ? sz : buf.len};
    614 }
    615 
    616 /** deserialize the arguments from the given buffer, using a separator.
    617  *
    618  * @return the number of characters read from the buffer, or csubstr::npos
    619  *   if a conversion was not successful
    620  * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */
    621 template<class Sep, class Arg, class... Args>
    622 size_t uncatsep(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
    623 {
    624     size_t ret = from_chars_first(buf, &a), num = ret;
    625     if(C4_UNLIKELY(ret == csubstr::npos))
    626         return csubstr::npos;
    627     buf  = buf.len >= ret ? buf.sub(ret) : substr{};
    628     ret  = detail::uncatsep_more(buf, sep, more...);
    629     if(C4_UNLIKELY(ret == csubstr::npos))
    630         return csubstr::npos;
    631     num += ret;
    632     return num;
    633 }
    634 
    635 
    636 //-----------------------------------------------------------------------------
    637 //-----------------------------------------------------------------------------
    638 //-----------------------------------------------------------------------------
    639 
    640 /// @cond dev
    641 // terminates the variadic recursion
    642 inline size_t format(substr buf, csubstr fmt)
    643 {
    644     return to_chars(buf, fmt);
    645 }
    646 /// @endcond
    647 
    648 
    649 /** using a format string, serialize the arguments into the given
    650  * fixed-size buffer.
    651  * The buffer size is strictly respected: no writes will occur beyond its end.
    652  * In the format string, each argument is marked with a compact
    653  * curly-bracket pair: {}. Arguments beyond the last curly bracket pair
    654  * are silently ignored. For example:
    655  * @code{.cpp}
    656  * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers
    657  * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees
    658  * @endcode
    659  * @return the number of characters needed to write into the buffer.
    660  * @see c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired
    661  * @see c4::unformat() for the inverse function
    662  * @see c4::cat() if no format or separator is needed
    663  * @see c4::catsep() if no format is needed, but a separator must be used */
    664 template<class Arg, class... Args>
    665 size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
    666 {
    667     size_t pos = fmt.find("{}"); // @todo use _find_fmt()
    668     if(C4_UNLIKELY(pos == csubstr::npos))
    669         return to_chars(buf, fmt);
    670     size_t num = to_chars(buf, fmt.sub(0, pos));
    671     size_t out = num;
    672     buf  = buf.len >= num ? buf.sub(num) : substr{};
    673     num  = to_chars(buf, a);
    674     out += num;
    675     buf  = buf.len >= num ? buf.sub(num) : substr{};
    676     num  = format(buf, fmt.sub(pos + 2), more...);
    677     out += num;
    678     return out;
    679 }
    680 
    681 /** like c4::format() but return a substr instead of a size
    682  * @see c4::format()
    683  * @see c4::catsep(). uncatsep() is the inverse of catsep(). */
    684 template<class... Args>
    685 substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args)
    686 {
    687     size_t sz = c4::format(buf, fmt, args...);
    688     C4_CHECK(sz <= buf.len);
    689     return {buf.str, sz <= buf.len ? sz : buf.len};
    690 }
    691 
    692 
    693 //-----------------------------------------------------------------------------
    694 
    695 /// @cond dev
    696 // terminates the variadic recursion
    697 inline size_t unformat(csubstr /*buf*/, csubstr fmt)
    698 {
    699     return fmt.len;
    700 }
    701 /// @endcond
    702 
    703 
    704 /** using a format string, deserialize the arguments from the given
    705  * buffer.
    706  * @return the number of characters read from the buffer, or npos if a conversion failed.
    707  * @see c4::format(). c4::unformat() is the inverse function to format(). */
    708 template<class Arg, class... Args>
    709 size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
    710 {
    711     const size_t pos = fmt.find("{}");
    712     if(C4_UNLIKELY(pos == csubstr::npos))
    713         return unformat(buf, fmt);
    714     size_t num = pos;
    715     size_t out = num;
    716     buf  = buf.len >= num ? buf.sub(num) : substr{};
    717     num  = from_chars_first(buf, &a);
    718     if(C4_UNLIKELY(num == csubstr::npos))
    719         return csubstr::npos;
    720     out += num;
    721     buf  = buf.len >= num ? buf.sub(num) : substr{};
    722     num  = unformat(buf, fmt.sub(pos + 2), more...);
    723     if(C4_UNLIKELY(num == csubstr::npos))
    724         return csubstr::npos;
    725     out += num;
    726     return out;
    727 }
    728 
    729 
    730 //-----------------------------------------------------------------------------
    731 //-----------------------------------------------------------------------------
    732 //-----------------------------------------------------------------------------
    733 
    734 /** like c4::cat(), but receives a container, and resizes it as needed to contain
    735  * the result. The container is overwritten. To append to it, use the append
    736  * overload.
    737  * @see c4::cat() */
    738 template<class CharOwningContainer, class... Args>
    739 inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
    740 {
    741 retry:
    742     substr buf = to_substr(*cont);
    743     size_t ret = cat(buf, args...);
    744     cont->resize(ret);
    745     if(ret > buf.len)
    746         goto retry;
    747 }
    748 
    749 /** like c4::cat(), but creates and returns a new container sized as needed to contain
    750  * the result.
    751  * @see c4::cat() */
    752 template<class CharOwningContainer, class... Args>
    753 inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args)
    754 {
    755     CharOwningContainer cont;
    756     catrs(&cont, args...);
    757     return cont;
    758 }
    759 
    760 /** like c4::cat(), but receives a container, and appends to it instead of
    761  * overwriting it. The container is resized as needed to contain the result.
    762  * @return the region newly appended to the original container
    763  * @see c4::cat()
    764  * @see c4::catrs() */
    765 template<class CharOwningContainer, class... Args>
    766 inline csubstr catrs_append(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
    767 {
    768     const size_t pos = cont->size();
    769 retry:
    770     substr buf = to_substr(*cont).sub(pos);
    771     size_t ret = cat(buf, args...);
    772     cont->resize(pos + ret);
    773     if(ret > buf.len)
    774         goto retry;
    775     return to_csubstr(*cont).range(pos, cont->size());
    776 }
    777 
    778 
    779 //-----------------------------------------------------------------------------
    780 
    781 /** like c4::catsep(), but receives a container, and resizes it as needed to contain the result.
    782  * The container is overwritten. To append to the container use the append overload.
    783  * @see c4::catsep() */
    784 template<class CharOwningContainer, class Sep, class... Args>
    785 inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
    786 {
    787 retry:
    788     substr buf = to_substr(*cont);
    789     size_t ret = catsep(buf, sep, args...);
    790     cont->resize(ret);
    791     if(ret > buf.len)
    792         goto retry;
    793 }
    794 
    795 /** like c4::catsep(), but create a new container with the result.
    796  * @return the requested container */
    797 template<class CharOwningContainer, class Sep, class... Args>
    798 inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
    799 {
    800     CharOwningContainer cont;
    801     catseprs(&cont, sep, args...);
    802     return cont;
    803 }
    804 
    805 
    806 /** like catsep(), but receives a container, and appends the arguments, resizing the
    807  * container as needed to contain the result. The buffer is appended to.
    808  * @return a csubstr of the appended part
    809  * @ingroup formatting_functions */
    810 template<class CharOwningContainer, class Sep, class... Args>
    811 inline csubstr catseprs_append(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
    812 {
    813     const size_t pos = cont->size();
    814 retry:
    815     substr buf = to_substr(*cont).sub(pos);
    816     size_t ret = catsep(buf, sep, args...);
    817     cont->resize(pos + ret);
    818     if(ret > buf.len)
    819         goto retry;
    820     return to_csubstr(*cont).range(pos, cont->size());
    821 }
    822 
    823 
    824 //-----------------------------------------------------------------------------
    825 
    826 /** like c4::format(), but receives a container, and resizes it as needed
    827  * to contain the result.  The container is overwritten. To append to
    828  * the container use the append overload.
    829  * @see c4::format() */
    830 template<class CharOwningContainer, class... Args>
    831 inline void formatrs(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
    832 {
    833 retry:
    834     substr buf = to_substr(*cont);
    835     size_t ret = format(buf, fmt, args...);
    836     cont->resize(ret);
    837     if(ret > buf.len)
    838         goto retry;
    839 }
    840 
    841 /** like c4::format(), but create a new container with the result.
    842  * @return the requested container */
    843 template<class CharOwningContainer, class... Args>
    844 inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args)
    845 {
    846     CharOwningContainer cont;
    847     formatrs(&cont, fmt, args...);
    848     return cont;
    849 }
    850 
    851 /** like format(), but receives a container, and appends the
    852  * arguments, resizing the container as needed to contain the
    853  * result. The buffer is appended to.
    854  * @return the region newly appended to the original container
    855  * @ingroup formatting_functions */
    856 template<class CharOwningContainer, class... Args>
    857 inline csubstr formatrs_append(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
    858 {
    859     const size_t pos = cont->size();
    860 retry:
    861     substr buf = to_substr(*cont).sub(pos);
    862     size_t ret = format(buf, fmt, args...);
    863     cont->resize(pos + ret);
    864     if(ret > buf.len)
    865         goto retry;
    866     return to_csubstr(*cont).range(pos, cont->size());
    867 }
    868 
    869 } // namespace c4
    870 
    871 #ifdef _MSC_VER
    872 #   pragma warning(pop)
    873 #elif defined(__clang__)
    874 #   pragma clang diagnostic pop
    875 #elif defined(__GNUC__)
    876 #   pragma GCC diagnostic pop
    877 #endif
    878 
    879 #endif /* _C4_FORMAT_HPP_ */