VLink 2.0.0
A high-performance communication middleware
Loading...
Searching...
No Matches
format.h
Go to the documentation of this file.
1/*
2 * Copyright (C) 2026 by Thun Lu. All rights reserved.
3 * Author: Thun Lu <thun.lu@zohomail.cn>
4 * Repo: https://github.com/thun-res/vlink
5 * _ __ __ _ __
6 * | | / / / / (_) ____ / /__
7 * | | / / / / / / / __ \ / //_/
8 * | |/ / / /___ / / / / / / / ,<
9 * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 */
23
24/**
25 * @file format.h
26 * @brief Lightweight header-only @c {} placeholder formatter with no dynamic allocation.
27 *
28 * @details
29 * This namespace provides a minimal subset of std::format-compatible formatting for use
30 * inside the VLink logger hot path where @c <format> may be unavailable (C++17) or too
31 * heavyweight. All formatting writes through a stack-allocated writer or a user-supplied
32 * output iterator and never touches the heap.
33 *
34 * Supported argument types:
35 *
36 * | C++ type | Format token | Example output |
37 * | ------------------------------- | ------------- | ------------------- |
38 * | int / short / signed char | @c {} | @c 42 |
39 * | unsigned / unsigned short / ... | @c {} | @c 42 |
40 * | long / long long | @c {} | @c 123456789 |
41 * | unsigned long / long long | @c {} | @c 123456789 |
42 * | bool | @c {} | @c true / @c false |
43 * | char | @c {} | @c A |
44 * | float / double | @c {} | @c 3.14 |
45 * | const char* / char* | @c {} | @c hello |
46 * | std::string / std::string_view | @c {} | @c hello |
47 * | T* (any pointer) | @c {} | @c 0x7ffe1234 |
48 * | enum (any) | @c {} | underlying int |
49 *
50 * Placeholder syntax:
51 * - @c {} -- consume arguments in order.
52 * - @c {0}, @c {1}, ... -- explicit positional indexing.
53 * - @c {{ / @c }} -- literal brace.
54 *
55 * Public API:
56 * - @c format_to_n(out, n, fmt, args...) -- writes at most @p n chars into a @c char* buffer.
57 * - @c format_to(out[N], fmt, args...) -- writes into a fixed array.
58 * - @c format_to(iterator, fmt, args...) -- writes to an output iterator.
59 *
60 * @note
61 * - Float and double are formatted via @c snprintf with @c "%g". There is no
62 * precision specifier in the placeholder syntax.
63 * - Custom types (those not listed above) trigger a @c static_assert at compile time.
64 * - Truncated @c format_to_n results set the @c truncated flag in the returned struct.
65 *
66 * @par Example
67 * @code
68 * char buf[128];
69 * auto result = vlink::format::format_to_n(buf, sizeof(buf) - 1, "x={} y={}", 3, 4.5);
70 * buf[result.size] = '\0';
71 * // buf == "x=3 y=4.5"
72 * @endcode
73 */
74
75#pragma once
76
77#include <cstddef>
78#include <cstdint>
79#include <cstdio>
80#include <cstring>
81#include <string>
82#include <string_view>
83#include <type_traits>
84#include <utility>
85
86#include "./macros.h"
87
88// NOLINTBEGIN
89
90namespace vlink {
91
92/**
93 * @namespace vlink::format
94 * @brief Lightweight header-only @c {} placeholder formatter.
95 */
96namespace format {
97
98namespace detail {
99
100template <typename T>
101using RemoveCvref = typename std::remove_cv_t<std::remove_reference_t<T>>;
102
103template <typename T, typename = void>
104struct IsOutputIteratorImpl : std::false_type {};
105
106template <typename T>
107struct IsOutputIteratorImpl<T, std::enable_if_t<std::is_assignable_v<decltype(*std::declval<T&>()++), char>>>
108 : std::true_type {};
109
110template <>
111struct IsOutputIteratorImpl<char*> : std::true_type {};
112
113// 数组全部排除
114template <typename T, size_t N>
115struct IsOutputIteratorImpl<T[N]> : std::false_type {};
116
117template <typename T>
119
134
135template <typename T>
136struct TypeConstant : std::integral_constant<Type, Type::kNone> {};
137
138template <>
139struct TypeConstant<signed char> : std::integral_constant<Type, Type::kInt> {};
140
141template <>
142struct TypeConstant<unsigned char> : std::integral_constant<Type, Type::kUint> {};
143
144template <>
145struct TypeConstant<short> : std::integral_constant<Type, Type::kInt> {};
146
147template <>
148struct TypeConstant<unsigned short> : std::integral_constant<Type, Type::kUint> {};
149
150template <>
151struct TypeConstant<int> : std::integral_constant<Type, Type::kInt> {};
152
153template <>
154struct TypeConstant<unsigned> : std::integral_constant<Type, Type::kUint> {};
155
156template <>
157struct TypeConstant<long> : std::integral_constant<Type, Type::kLongLong> {};
158
159template <>
160struct TypeConstant<unsigned long> : std::integral_constant<Type, Type::kUlongLong> {};
161
162template <>
163struct TypeConstant<long long> : std::integral_constant<Type, Type::kLongLong> {};
164
165template <>
166struct TypeConstant<unsigned long long> : std::integral_constant<Type, Type::kUlongLong> {};
167
168template <>
169struct TypeConstant<bool> : std::integral_constant<Type, Type::kBool> {};
170
171template <>
172struct TypeConstant<char> : std::integral_constant<Type, Type::kChar> {};
173
174template <>
175struct TypeConstant<float> : std::integral_constant<Type, Type::kFloat> {};
176
177template <>
178struct TypeConstant<double> : std::integral_constant<Type, Type::kDouble> {};
179
180template <>
181struct TypeConstant<const char*> : std::integral_constant<Type, Type::kCstring> {};
182
183template <>
184struct TypeConstant<char*> : std::integral_constant<Type, Type::kCstring> {};
185
186template <>
187struct TypeConstant<std::string_view> : std::integral_constant<Type, Type::kString> {};
188
189template <>
190struct TypeConstant<std::string> : std::integral_constant<Type, Type::kString> {};
191
192template <size_t N>
193struct TypeConstant<char[N]> : std::integral_constant<Type, Type::kCstring> {};
194
195template <size_t N>
196struct TypeConstant<const char[N]> : std::integral_constant<Type, Type::kCstring> {};
197
198template <typename T>
199struct TypeConstant<T*> : std::integral_constant<Type, Type::kPointer> {};
200
201template <typename UIntT>
202inline int count_digits(UIntT n) {
203 int count = 1;
204
205 while (n >= 10) {
206 n /= 10;
207 ++count;
208 }
209
210 return count;
211}
212
213template <typename CharT, typename UIntT>
214inline CharT* write_int_digits(CharT* buf, UIntT value, int num_digits) {
215 CharT* end = buf + num_digits;
216
217 while (value >= 10) {
218 unsigned digit = static_cast<unsigned>(value % 10);
219 *--end = static_cast<CharT>('0' + digit);
220 value /= 10;
221 }
222
223 *--end = static_cast<CharT>('0' + value);
224
225 return buf + num_digits;
226}
227
229 public:
230 inline StringWriter(char* buf, size_t size) noexcept : begin_(buf), ptr_(buf), end_(buf + size) {}
231
232 inline char* out() const noexcept { return ptr_; }
233
234 inline size_t written() const noexcept { return static_cast<size_t>(ptr_ - begin_); }
235
236 inline size_t total_size() const noexcept { return total_size_; }
237
238 inline void write(char c) {
239 ++total_size_;
240
241 if (ptr_ < end_) {
242 *ptr_++ = c;
243 }
244 }
245
246 inline void write(const char* s, size_t count) {
247 total_size_ += count;
248
249 size_t avail = static_cast<size_t>(end_ - ptr_);
250 size_t n = (count <= avail) ? count : avail;
251
252 if (n > 0) {
253 std::memcpy(ptr_, s, n);
254 ptr_ += n;
255 }
256 }
257
258 inline void write(std::string_view sv) { write(sv.data(), sv.size()); }
259
260 private:
261 char* begin_{nullptr};
262 char* ptr_{nullptr};
263 char* end_{nullptr};
264 size_t total_size_{0};
265};
266
267template <typename OutputItT>
269 public:
270 inline explicit IteratorWriter(OutputItT out) : out_(out) {}
271
272 inline OutputItT out() const noexcept { return out_; }
273
274 inline size_t size() const noexcept { return count_; }
275
276 inline void write(char c) {
277 *out_++ = c;
278 ++count_;
279 }
280
281 inline void write(const char* s, size_t count) {
282 for (size_t i = 0; i < count; ++i) {
283 *out_++ = s[i];
284 }
285
286 count_ += count;
287 }
288
289 inline void write(std::string_view sv) { write(sv.data(), sv.size()); }
290
291 private:
292 OutputItT out_;
293 size_t count_{0};
294};
295
296template <typename CharT>
297class Value {
298 public:
299 union {
301 unsigned uint_value;
303 unsigned long long ulong_long_value;
308 const CharT* string_value;
309 std::string_view string_view_value;
310 const void* pointer_value;
311 };
312
313 constexpr Value() : int_value(0) {}
314
315 constexpr Value(signed char val) : int_value(static_cast<int>(val)) {}
316
317 constexpr Value(unsigned char val) : uint_value(static_cast<unsigned>(val)) {}
318
319 constexpr Value(short val) : int_value(static_cast<int>(val)) {}
320
321 constexpr Value(unsigned short val) : uint_value(static_cast<unsigned>(val)) {}
322
323 constexpr Value(int val) : int_value(val) {}
324
325 constexpr Value(unsigned val) : uint_value(val) {}
326
327 constexpr Value(long val) : long_long_value(val) {}
328
329 constexpr Value(unsigned long val) : ulong_long_value(val) {}
330
331 constexpr Value(long long val) : long_long_value(val) {}
332
333 constexpr Value(unsigned long long val) : ulong_long_value(val) {}
334
335 constexpr Value(bool val) : bool_value(val) {}
336
337 constexpr Value(CharT val) : char_value(val) {}
338
339 constexpr Value(float val) : float_value(val) {}
340
341 constexpr Value(double val) : double_value(val) {}
342
343 constexpr Value(const CharT* val) : string_value(val) {}
344
345 constexpr Value(CharT* val) : string_value(val) {}
346
347 constexpr Value(std::string_view val) : string_view_value(val) {}
348
349 constexpr Value(const std::string& val) : string_view_value(val) {}
350
351 template <size_t N>
352 constexpr Value(const CharT (&val)[N]) : string_value(val) {}
353
354 template <size_t N>
355 constexpr Value(CharT (&val)[N]) : string_value(val) {}
356
357 template <typename T>
358 constexpr Value(T* val) : pointer_value(static_cast<const void*>(val)) {}
359};
360
361template <typename CharT>
363 public:
364 constexpr FormatArg() {}
365
366 template <typename T>
367 constexpr FormatArg(const T& val) {
368 if constexpr (std::is_enum_v<T>) {
369 using U = std::underlying_type_t<T>;
370 value_ = Value<CharT>(static_cast<U>(val));
372 } else if constexpr (TypeConstant<RemoveCvref<T>>::value != Type::kNone) {
373 value_ = Value<CharT>(val);
375 } else {
376 static_assert(!sizeof(T),
377 "[vlink::format] unsupported type for format_to/MLOG, "
378 "convert to string first");
379 }
380 }
381
382 constexpr Type type() const { return type_; }
383 constexpr const Value<CharT>& value() const { return value_; }
384
385 private:
386 Value<CharT> value_;
387 Type type_{Type::kNone};
388};
389
390template <typename CharT, typename... ArgsT>
392 static constexpr size_t kNumArgs = sizeof...(ArgsT);
394
395 template <typename... T>
396 constexpr FormatArgStore(const T&... values) : args{FormatArg<CharT>(values)...} {}
397};
398
399template <typename CharT>
401 public:
402 constexpr BasicFormatArgs() : args_(nullptr), size_(0) {}
403
404 template <typename... ArgsT>
406 : args_(store.args), size_(sizeof...(ArgsT)) {}
407
408 constexpr FormatArg<CharT> get(size_t id) const { return id < size_ ? args_[id] : FormatArg<CharT>(); }
409
410 constexpr size_t size() const { return size_; }
411
412 private:
413 const FormatArg<CharT>* args_;
414 size_t size_;
415};
416
418
419template <typename CharT, typename WriterT>
421 public:
422 inline explicit FormatWriter(WriterT writer) : writer_(writer) {}
423
424 void format(std::string_view fmt, BasicFormatArgs<CharT> args) {
425 size_t arg_id = 0;
426 const char* p = fmt.data();
427 const char* end = p + fmt.size();
428
429 while (p != end) {
430 char c = *p++;
431
432 if (c == '}') {
433 if (p != end && *p == '}') {
434 writer_.write('}');
435 ++p;
436 } else {
437 writer_.write('}');
438 }
439
440 continue;
441 }
442
443 if (c != '{') {
444 writer_.write(c);
445 continue;
446 }
447
448 if (p == end) {
449 writer_.write('{');
450 break;
451 }
452
453 if (*p == '{') {
454 writer_.write('{');
455 ++p;
456 continue;
457 }
458
459 if (*p == '}') {
460 if (arg_id < args.size()) {
461 write_arg(args.get(arg_id++));
462 }
463
464 ++p;
465
466 continue;
467 }
468
469 size_t index = arg_id;
470 bool has_explicit_index = false;
471
472 if (*p >= '0' && *p <= '9') {
473 index = 0;
474 has_explicit_index = true;
475
476 while (p != end && *p >= '0' && *p <= '9') {
477 index = index * 10 + static_cast<size_t>(*p++ - '0');
478 }
479 }
480
481 while (p != end && *p != '}') {
482 ++p;
483 }
484
485 if (p != end) {
486 if (index < args.size()) {
487 write_arg(args.get(index));
488 }
489
490 if (!has_explicit_index) {
491 ++arg_id;
492 }
493
494 ++p;
495 }
496 }
497 }
498
499 inline auto out() const { return writer_.out(); }
500
501 template <typename W = WriterT>
502 inline auto total_size() const -> decltype(std::declval<W>().total_size()) {
503 return writer_.total_size();
504 }
505
506 inline size_t size() const { return writer_.size(); }
507
508 private:
509 void write_int(int value) {
510 if (value < 0) {
511 writer_.write('-');
512 write_uint(static_cast<unsigned>(-(value + 1)) + 1);
513 } else {
514 write_uint(static_cast<unsigned>(value));
515 }
516 }
517
518 void write_uint(unsigned value) {
519 char buf[10];
520 int num_digits = count_digits(value);
521 write_int_digits(buf, value, num_digits);
522 writer_.write(buf, static_cast<size_t>(num_digits));
523 }
524
525 void write_long_long(long long value) {
526 if (value < 0) {
527 writer_.write('-');
528 write_ulong_long(static_cast<unsigned long long>(-(value + 1)) + 1);
529 } else {
530 write_ulong_long(static_cast<unsigned long long>(value));
531 }
532 }
533
534 void write_ulong_long(unsigned long long value) {
535 char buf[20];
536 int num_digits = count_digits(value);
537 write_int_digits(buf, value, num_digits);
538 writer_.write(buf, static_cast<size_t>(num_digits));
539 }
540
541 void write_bool(bool value) {
542 if (value) {
543 writer_.write("true", 4);
544 } else {
545 writer_.write("false", 5);
546 }
547 }
548
549 void write_char(char value) { writer_.write(value); }
550
551 void write_string(const char* str) {
552 if VLIKELY (str) {
553 writer_.write(str, std::strlen(str));
554 } else {
555 writer_.write("(null)", 6);
556 }
557 }
558
559 void write_string_view(std::string_view sv) { writer_.write(sv); }
560
561 void write_pointer(const void* ptr) {
562 static constexpr const char kHexDigits[] = "0123456789abcdef";
563 static_assert(sizeof(uintptr_t) <= 8, "pointer size > 64bit not supported");
564
565 char buf[16];
566 int i = 16;
567 auto value = reinterpret_cast<uintptr_t>(ptr);
568
569 writer_.write("0x", 2);
570
571 do {
572 buf[--i] = kHexDigits[value & 0xF];
573 value >>= 4;
574 } while (value != 0);
575
576 writer_.write(buf + i, static_cast<size_t>(16 - i));
577 }
578
579 void write_float(float value) {
580 char buf[32];
581 int len = snprintf(buf, sizeof(buf), "%g", static_cast<double>(value));
582
583 if (len > 0 && len < static_cast<int>(sizeof(buf))) {
584 writer_.write(buf, static_cast<size_t>(len));
585 }
586 }
587
588 void write_double(double value) {
589 char buf[32];
590 int len = snprintf(buf, sizeof(buf), "%g", value);
591
592 if (len > 0 && len < static_cast<int>(sizeof(buf))) {
593 writer_.write(buf, static_cast<size_t>(len));
594 }
595 }
596
597 void write_arg(const FormatArg<CharT>& arg) {
598 switch (arg.type()) {
599 case Type::kInt:
600 write_int(arg.value().int_value);
601 break;
602 case Type::kUint:
603 write_uint(arg.value().uint_value);
604 break;
605 case Type::kLongLong:
606 write_long_long(arg.value().long_long_value);
607 break;
608 case Type::kUlongLong:
609 write_ulong_long(arg.value().ulong_long_value);
610 break;
611 case Type::kBool:
612 write_bool(arg.value().bool_value);
613 break;
614 case Type::kChar:
615 write_char(arg.value().char_value);
616 break;
617 case Type::kFloat:
618 write_float(arg.value().float_value);
619 break;
620 case Type::kDouble:
621 write_double(arg.value().double_value);
622 break;
623 case Type::kCstring:
624 write_string(arg.value().string_value);
625 break;
626 case Type::kString:
627 write_string_view(arg.value().string_view_value);
628 break;
629 case Type::kPointer:
630 write_pointer(arg.value().pointer_value);
631 break;
632 default:
633 break;
634 }
635 }
636
637 WriterT writer_;
638};
639
640} // namespace detail
641
642/**
643 * @struct FString
644 * @brief Compile-time format string wrapper that carries type information about arguments.
645 *
646 * @details
647 * Acts as a thin @c std::string_view wrapper tagged with the argument type list.
648 * This enables type-safe format_to_n / format_to calls without dynamic dispatch.
649 *
650 * @tparam T Argument types (unused at runtime but encode the expected argument list).
651 */
652template <typename... T>
653struct FString {
654 std::string_view str;
655 using t = FString;
656
657 template <size_t N>
658 constexpr FString(const char (&s)[N]) : str(s, N - 1) {}
659
660 template <typename StrT, std::enable_if_t<std::is_convertible_v<const StrT&, std::string_view>, int> = 0>
661 constexpr FString(const StrT& s) : str(s) {}
662
663 inline operator std::string_view() const { return str; }
664 std::string_view get() const { return str; }
665};
666
667/**
668 * @brief Alias for @c FString used as the type of format-string parameters.
669 *
670 * @details
671 * @c format_string<ArgsT...> is the type of the format argument in @c format_to_n
672 * and @c format_to. Constructed implicitly from string literals.
673 *
674 * @tparam T Expected argument types (for documentation; not enforced at runtime).
675 */
676template <typename... T>
677using format_string = typename FString<T...>::t;
678
679/**
680 * @brief Creates a type-erased argument store from a variadic argument list.
681 *
682 * @tparam ArgsT Argument types.
683 * @param args Arguments to capture.
684 * @return @c FormatArgStore containing the erased argument values.
685 */
686template <typename... ArgsT>
688 return {args...};
689}
690
691/**
692 * @struct FormatToNResult
693 * @brief Result type for @c format_to_n, carrying the output pointer, written size and truncation flag.
694 *
695 * @tparam OutputItT Output iterator or pointer type.
696 */
697template <typename OutputItT>
699 OutputItT out; ///< Pointer/iterator one past the last written character.
700 size_t size{0}; ///< Total number of characters that would have been written (may exceed n).
701 bool truncated{false}; ///< @c true if the output was truncated because @c size > n.
702};
703
704/**
705 * @brief Formats arguments into a @c char* buffer, writing at most @p n characters.
706 *
707 * @details
708 * Placeholders (@c {}) in @p fmt are replaced in order by the corresponding argument from
709 * @p args. If more characters would be produced than @p n, output is truncated and the
710 * returned @c truncated flag is set. The caller must null-terminate the output if needed.
711 *
712 * @tparam ArgsT Argument types (deduced).
713 * @param out Destination buffer. Must have capacity of at least @p n bytes.
714 * @param n Maximum number of characters to write (not counting a null terminator).
715 * @param fmt Format string with @c {} placeholders.
716 * @param args Format arguments.
717 * @return @c FormatToNResult with the end pointer, total size and truncation flag.
718 */
719template <typename... ArgsT>
720inline FormatToNResult<char*> format_to_n(char* out, size_t n, format_string<ArgsT...> fmt, const ArgsT&... args) {
722
723 detail::FormatArgs fargs(arg_store);
724 detail::StringWriter sw(out, n);
726 writer.format(fmt.get(), fargs);
727
728 size_t total = writer.total_size();
729 return {writer.out(), total, total > n};
730}
731
732/**
733 * @struct FormatToResult
734 * @brief Result type for the fixed-array overload of @c format_to.
735 */
737 char* out; ///< Pointer one past the last written character.
738 size_t size; ///< Total characters that would have been written.
739 bool truncated; ///< @c true if output was truncated.
740};
741
742/**
743 * @brief Formats arguments into a fixed-size char array.
744 *
745 * @details
746 * The array size @p N is deduced automatically. Equivalent to
747 * @c format_to_n(out, N, fmt, args...).
748 *
749 * @tparam N Array size (deduced).
750 * @tparam ArgsT Argument types.
751 * @param out Destination char array.
752 * @param fmt Format string.
753 * @param args Format arguments.
754 * @return @c FormatToResult with end pointer, total size and truncation flag.
755 */
756template <size_t N, typename... ArgsT>
757inline FormatToResult format_to(char (&out)[N], format_string<ArgsT...> fmt, const ArgsT&... args) {
758 auto result = ::vlink::format::format_to_n(out, N, fmt, args...);
759 return {result.out, result.size, result.truncated};
760}
761
762/**
763 * @brief Formats arguments to an output iterator.
764 *
765 * @details
766 * Writes each character via @c *out++ = c. The iterator must model the
767 * @c OutputIterator concept (assignable and dereferenceable).
768 *
769 * @tparam OutputItT Output iterator type.
770 * @tparam ArgsT Argument types.
771 * @param out Destination output iterator.
772 * @param fmt Format string with @c {} placeholders.
773 * @param args Format arguments.
774 * @return The iterator one past the last written character.
775 */
776template <typename OutputItT, typename... ArgsT,
777 std::enable_if_t<detail::kIsOutputIterator<detail::RemoveCvref<OutputItT>> &&
778 !std::is_array_v<std::remove_reference_t<OutputItT>>,
779 int> = 0>
780inline detail::RemoveCvref<OutputItT> format_to(OutputItT&& out, format_string<ArgsT...> fmt, const ArgsT&... args) {
782 auto arg_store = ::vlink::format::make_format_args(args...);
783 detail::FormatArgs fargs(arg_store);
784 detail::IteratorWriter<ItT> iter_writer(out);
786 writer.format(fmt.get(), fargs);
787 return writer.out();
788}
789
790} // namespace format
791
792} // namespace vlink
793
794// NOLINTEND
Platform-independent macro definitions for the VLink library.
#define VLIKELY(...)
Shorthand alias for VLINK_LIKELY. Hints that the expression is likely true.
Definition macros.h:297
STL namespace.