microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
microfmt.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
10#include <algorithm>
11#include <array>
12#include <cstddef>
13#include <cstdint>
14#include <string_view>
15#include <tuple>
16#include <type_traits>
17
18#include "detail/assert.hpp"
19#include "detail/compat.hpp"
20#include "reloco.hpp"
21
22#include <reloco/fmt.hpp>
23
24namespace microfmt {
25
26// ============================================================================
27// Type-Erased Callback Sink
28// ============================================================================
29
46using sink = reloco::sink;
47
48// ============================================================================
49// Concrete Sinks
50// ============================================================================
51
60class MICROFMT_API_CLASS RELOCO_POINTER span_sink {
61public:
68 explicit constexpr span_sink(span<char> buf RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
69 : m_buf(buf), m_pos(0) {}
70
71#if RELOCO_HAS_STD_SPAN
72 // std-interop-begin: opt-in constructor from std::span for interop with
73 // APIs that already speak <span>; reloco::span needs no such adapter.
80 template <std::size_t Extent>
81 explicit constexpr span_sink(
82 std::span<char, Extent> buf RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
83 : m_buf(buf.data(), buf.size()), m_pos(0) {}
84 // std-interop-end
85#endif
86
93 [[nodiscard]] sink as_sink() noexcept RELOCO_LIFETIMEBOUND {
94 return sink{this, [](void *ctx, microfmt::string_view sv) noexcept {
95 auto *self = static_cast<span_sink *>(ctx);
96 const size_t avail = (self->m_pos < self->m_buf.size()) ? (self->m_buf.size() - self->m_pos) : 0;
97 const size_t n = std::min(sv.size(), avail);
98 if (n > 0) {
99 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
100 std::copy_n(sv.data(), n, self->m_buf.data() + self->m_pos);
101 RELOCO_END_UNSAFE_BUFFER_USAGE;
102 self->m_pos += n;
103 }
104 }};
105 }
106
112 [[nodiscard]] constexpr microfmt::string_view view() const noexcept RELOCO_LIFETIMEBOUND {
113 return microfmt::string_view(m_buf.data(), m_pos);
114 }
115
121 [[nodiscard]] constexpr size_t size() const noexcept { return m_pos; }
122
128 [[nodiscard]] constexpr size_t available() const noexcept {
129 return (m_pos < m_buf.size()) ? (m_buf.size() - m_pos) : 0;
130 }
131
135 RELOCO_REINITIALIZES constexpr void reset() noexcept { m_pos = 0; }
136
137private:
138 span<char> m_buf;
139 size_t m_pos{0};
140};
141
142namespace detail {
143
144struct MICROFMT_API_CLASS RELOCO_POINTER buffer_sink_base {
145 char *m_data{};
146 std::size_t m_capacity{};
147 std::size_t m_pos{};
148
149 constexpr buffer_sink_base(char *ptr RELOCO_LIFETIMEBOUND, std::size_t cap, size_t pos) noexcept
150 : m_data(ptr), m_capacity(cap), m_pos(pos) {}
151
152 // Derived classes **MUST** provide proper overrides
153 buffer_sink_base(const buffer_sink_base &) = delete;
154 buffer_sink_base &operator=(const buffer_sink_base &) = delete;
155 buffer_sink_base(buffer_sink_base &&) = delete;
156 buffer_sink_base &operator=(buffer_sink_base &&) = delete;
157
158 RELOCO_CONSTEXPR20 static void write_thunk(void *ctx, microfmt::string_view sv) noexcept {
159 auto *self = static_cast<buffer_sink_base *>(ctx);
160 const std::size_t avail = (self->m_pos < self->m_capacity) ? (self->m_capacity - self->m_pos) : 0;
161 const std::size_t n = std::min(sv.size(), avail);
162 if (n > 0) {
163 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
164 std::copy_n(sv.data(), n, self->m_data + self->m_pos);
165 RELOCO_END_UNSAFE_BUFFER_USAGE;
166 self->m_pos += n;
167 }
168 }
169
170 RELOCO_REINITIALIZES void reset() noexcept { m_pos = 0; }
171};
172
173} // namespace detail
174
185template <std::size_t N> class buffer_sink : detail::buffer_sink_base {
186public:
190 constexpr buffer_sink() noexcept : detail::buffer_sink_base{m_storage, N, 0} {}
191 ~buffer_sink() = default;
192
193 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE
194 constexpr buffer_sink(const buffer_sink &other) noexcept : detail::buffer_sink_base{m_storage, N, other.m_pos} {
195 // Physically copy the written bytes
196 for (std::size_t i = 0; i < other.m_pos; ++i) {
197 m_storage[i] = other.m_storage[i];
198 }
199 }
200
201 constexpr buffer_sink(buffer_sink &&other) noexcept : detail::buffer_sink_base{m_storage, N, other.m_pos} {
202 for (std::size_t i = 0; i < other.m_pos; ++i) {
203 m_storage[i] = other.m_storage[i];
204 }
205 other.reset(); // Leave moved-from object safely empty
206 }
207
208 constexpr buffer_sink &operator=(const buffer_sink &other) noexcept {
209 if (this != &other) {
210 this->m_pos = other.m_pos;
211 for (std::size_t i = 0; i < other.m_pos; ++i) {
212 m_storage[i] = other.m_storage[i];
213 }
214 }
215 return *this;
216 }
217
218 constexpr buffer_sink &operator=(buffer_sink &&other) noexcept {
219 if (this != &other) {
220 this->m_pos = other.m_pos;
221 for (std::size_t i = 0; i < other.m_pos; ++i) {
222 m_storage[i] = other.m_storage[i];
223 }
224 other.reset();
225 }
226 return *this;
227 }
228 RELOCO_END_UNSAFE_BUFFER_USAGE
229
236 [[nodiscard]] RELOCO_CONSTEXPR20 sink as_sink() & noexcept RELOCO_LIFETIMEBOUND {
237 return sink{this, &detail::buffer_sink_base::write_thunk};
238 }
239
245 [[nodiscard]] constexpr microfmt::string_view view() const & noexcept RELOCO_LIFETIMEBOUND {
246 return microfmt::string_view(m_data, m_pos);
247 }
248
249 [[nodiscard]] constexpr microfmt::string_view view() const && = delete;
250
256 [[nodiscard]] constexpr std::size_t size() const noexcept { return m_pos; }
257
263 [[nodiscard]] constexpr std::size_t capacity() const noexcept { return N; }
264
270 [[nodiscard]] constexpr std::size_t available() const noexcept { return (m_pos < N) ? (N - m_pos) : 0; }
271
278 [[nodiscard]] constexpr span<const char> as_span() const & noexcept RELOCO_LIFETIMEBOUND {
279 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
280
281 return span<const char>(m_data, m_pos);
282
283 RELOCO_END_UNSAFE_BUFFER_USAGE;
284 }
285
286 [[nodiscard]] constexpr span<const char> as_span() const && = delete;
287
288#if RELOCO_HAS_STD_SPAN
289 // std-interop-begin: opt-in conversion to std::span for interop with
290 // APIs that already speak <span>; reloco::span needs no such adapter.
297 [[nodiscard]] constexpr std::span<const char> as_std_span() const & noexcept RELOCO_LIFETIMEBOUND {
298 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
299
300 return std::span<const char>(m_data, m_pos);
301
302 RELOCO_END_UNSAFE_BUFFER_USAGE;
303 }
304
305 [[nodiscard]] constexpr std::span<const char> as_std_span() const && = delete;
306 // std-interop-end
307#endif
308
312 RELOCO_REINITIALIZES constexpr void reset() noexcept { m_pos = 0; }
313
314private:
315 char m_storage[N]{};
316};
317
327template <typename OutputIt> class iterator_sink {
328public:
334 explicit constexpr iterator_sink(OutputIt it) noexcept : m_it(it) {}
335
342 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
343 // here.
344 RELOCO_BEGIN_UNSAFE_CODE
345 [[nodiscard]] sink as_sink() & noexcept RELOCO_LIFETIMEBOUND {
346 return sink{this, [](void *ctx, microfmt::string_view sv) noexcept {
347 auto *self = static_cast<iterator_sink<OutputIt> *>(ctx);
348// Guard against std::terminate in the noexcept callback
349#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
350 try {
351 self->m_it = std::copy(sv.begin(), sv.end(), self->m_it);
352 } catch (...) { // std-interop-ok: swallow any exception thrown by the user-supplied OutputIt to
353 // preserve the noexcept guarantee
354 // Swallow the exception to maintain noexcept guarantees.
355 // The output will simply be truncated from the point of failure.
356 }
357#else
358 // In -fno-exceptions environments, standard containers will
359 // typically invoke abort() directly on allocation failure.
360 self->m_it = std::copy(sv.begin(), sv.end(), self->m_it);
361#endif
362 }};
363 }
365
371 [[nodiscard]] constexpr OutputIt current() const noexcept { return m_it; }
372
373private:
374 OutputIt m_it;
375};
376
385public:
389 constexpr counting_sink() noexcept = default;
390
397 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
398 // here.
399 RELOCO_BEGIN_UNSAFE_CODE
400 [[nodiscard]] sink as_sink() & noexcept RELOCO_LIFETIMEBOUND {
401 return sink{this, [](void *ctx, microfmt::string_view sv) noexcept {
402 auto *self = static_cast<counting_sink *>(ctx);
403 self->m_count += sv.size();
404 }};
405 }
407
413 [[nodiscard]] constexpr std::size_t count() const noexcept { return m_count; }
414
418 RELOCO_REINITIALIZES constexpr void reset() noexcept { m_count = 0; }
419
420private:
421 std::size_t m_count{0};
422};
423
431public:
438 [[nodiscard]] static constexpr sink as_sink() noexcept {
439 return sink{nullptr, [](void *, microfmt::string_view) noexcept {}};
440 } // namespace microfmt
441};
442
443namespace detail {
444
446 char *m_data{};
447 std::size_t m_max_payload{}; // N - 1
448 std::size_t m_pos{};
449
450 constexpr c_string_sink_base(char *ptr RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS, size_t max,
451 size_t pos) noexcept
452 : m_data(ptr), m_max_payload(max), m_pos(pos) {}
453
454 // Derived classes **MUST** provide proper overrides
459
460 RELOCO_CONSTEXPR20 static void write_thunk(void *ctx, microfmt::string_view sv) noexcept {
461 auto *self = static_cast<c_string_sink_base *>(ctx);
462 const std::size_t avail = (self->m_pos < self->m_max_payload) ? (self->m_max_payload - self->m_pos) : 0;
463 const std::size_t n = std::min(sv.size(), avail);
464
465 if (n > 0) {
466 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
467 std::copy_n(sv.data(), n, self->m_data + self->m_pos);
468 self->m_pos += n;
469 self->m_data[self->m_pos] = '\0';
470 RELOCO_END_UNSAFE_BUFFER_USAGE;
471 }
472 }
473};
474
475} // namespace detail
476
486template <std::size_t N> class c_string_sink : detail::c_string_sink_base {
487 static_assert(N > 0, "c_string_sink buffer size must be at least 1 byte");
488
489public:
493 constexpr c_string_sink() noexcept : detail::c_string_sink_base{m_storage, N - 1, 0} { m_storage[0] = '\0'; }
494 ~c_string_sink() = default;
495
496 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE
497 constexpr c_string_sink(const c_string_sink &other) noexcept : detail::c_string_sink_base{m_storage, N, other.m_pos} {
498 // Physically copy the written bytes
499 for (std::size_t i = 0; i < other.m_pos; ++i) {
500 m_storage[i] = other.m_storage[i];
501 }
502 m_storage[other.m_pos] = '\0';
503 }
504
505 constexpr c_string_sink(c_string_sink &&other) noexcept : detail::c_string_sink_base{m_storage, N, other.m_pos} {
506 for (std::size_t i = 0; i < other.m_pos; ++i) {
507 m_storage[i] = other.m_storage[i];
508 }
509 m_storage[other.m_pos] = '\0';
510 other.reset(); // Leave moved-from object safely empty
511 }
512
513 constexpr c_string_sink &operator=(const c_string_sink &other) noexcept {
514 if (this != &other) {
515 this->m_pos = other.m_pos;
516 for (std::size_t i = 0; i < other.m_pos; ++i) {
517 m_storage[i] = other.m_storage[i];
518 }
519 m_storage[other.m_pos] = '\0';
520 }
521 return *this;
522 }
523
524 constexpr c_string_sink &operator=(c_string_sink &&other) noexcept {
525 if (this != &other) {
526 this->m_pos = other.m_pos;
527 for (std::size_t i = 0; i < other.m_pos; ++i) {
528 m_storage[i] = other.m_storage[i];
529 }
530 m_storage[other.m_pos] = '\0';
531 other.reset();
532 }
533 return *this;
534 }
535 RELOCO_END_UNSAFE_BUFFER_USAGE
536
543 [[nodiscard]] sink as_sink() & noexcept RELOCO_LIFETIMEBOUND {
545 }
546
552 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
553 // here.
554 RELOCO_BEGIN_UNSAFE_CODE
555 [[nodiscard]] constexpr const char *c_str() const & noexcept RELOCO_LIFETIMEBOUND { return m_data; }
557
558 [[nodiscard]] constexpr const char *c_str() const && = delete;
559
566 [[nodiscard]] constexpr microfmt::string_view view() const & noexcept RELOCO_LIFETIMEBOUND {
567 return microfmt::string_view(m_data, m_pos);
568 }
569
570 [[nodiscard]] constexpr microfmt::string_view view() const && = delete;
571
578 [[nodiscard]] constexpr std::size_t size() const noexcept { return m_pos; }
579
586 [[nodiscard]] static constexpr std::size_t max_size() noexcept { return N - 1; }
587
591 RELOCO_REINITIALIZES constexpr void reset() noexcept {
592 m_pos = 0;
593 m_data[0] = '\0';
594 }
595
596private:
597 char m_storage[N]{};
598};
599
607template <typename Callable> class RELOCO_POINTER callback_sink {
608public:
614 explicit constexpr callback_sink(Callable &fn RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
615 : m_fn(&fn) {}
616
623 [[nodiscard]] sink as_sink() noexcept RELOCO_LIFETIMEBOUND {
624 return sink{m_fn.get(), [](void *ctx, microfmt::string_view sv) noexcept {
625 auto *fn = static_cast<Callable *>(ctx);
626 (*fn)(sv);
627 }};
628 }
629
630private:
631 value_ptr<Callable> m_fn;
632};
633
642template <typename Callable>
643[[nodiscard]] constexpr callback_sink<Callable> make_callback_sink(Callable &fn RELOCO_LIFETIMEBOUND) noexcept {
644 return callback_sink<Callable>(fn);
645}
646
647// ============================================================================
648// Formatting Helper Algorithms (Minimal Stack)
649// ============================================================================
650
651namespace detail {
652
653// 2-digit lookup table for values 00-99
654inline constexpr auto digits_lut = []() {
655 array<char, 200> arr{};
656 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
657 for (int i = 0; i < 100; ++i) {
658 arr.unsafe_at(2 * static_cast<size_t>(i)) = static_cast<char>('0' + (i / 10));
659 arr.unsafe_at(2 * static_cast<size_t>(i) + 1) = static_cast<char>('0' + (i % 10));
660 }
661 RELOCO_END_UNSAFE_BUFFER_USAGE;
662 return arr;
663}();
664
665inline constexpr reloco::array<char, 16> hex_digits_lower = {'0', '1', '2', '3', '4', '5', '6', '7',
666 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
667
668inline constexpr reloco::array<char, 16> hex_digits_upper = {'0', '1', '2', '3', '4', '5', '6', '7',
669 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
670
679enum class radix : uint32_t { binary = 2, decimal = 10, hex = 16 };
680
681// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
682RELOCO_BEGIN_UNSAFE_CODE
683template <detail::radix Radix>
684RELOCO_ALWAYS_INLINE inline void format_integer_core(const sink &out, uint64_t val, bool is_negative, bool uppercase,
685 int min_width) noexcept {
686 static_assert(Radix == radix::decimal || Radix == radix::hex,
687 "format_integer_core only supports decimal/hex; use format_binary for radix::binary");
688 // Buffer sized exactly for the selected radix instead of the widest
689 // possible base (binary, handled separately by format_binary): 20 digits
690 // is the maximum for a decimal uint64_t (18446744073709551615), plus one
691 // slot for a '-' sign; 16 nibbles cover the maximum hex uint64_t, and hex
692 // output is never negative (format_signed only emits decimal), so no sign
693 // slot is needed there.
694 constexpr size_t buf_size = (Radix == radix::hex) ? 16 : 21;
695 microfmt::array<char, buf_size> buf; // Reclaimed immediately upon leaf exit
696 size_t idx = buf.size();
697
698 if (val == 0) {
699 buf[--idx] = '0';
700 } else if constexpr (Radix == radix::hex) {
701 const auto &hex_digits = uppercase ? hex_digits_upper : hex_digits_lower;
702 while (val > 0) {
703 buf[--idx] = hex_digits[val & 0xF];
704 val >>= 4;
705 }
706 } else {
707 while (val >= 100) {
708 const auto rem = static_cast<uint32_t>(val % 100);
709 val /= 100;
710 idx -= 2;
711 buf[idx] = digits_lut[rem * 2];
712 buf[idx + 1] = digits_lut[rem * 2 + 1];
713 }
714 if (val < 10) {
715 buf[--idx] = static_cast<char>('0' + val);
716 } else {
717 const auto rem = static_cast<uint32_t>(val * 2);
718 idx -= 2;
719 buf[idx] = digits_lut[rem];
720 buf[idx + 1] = digits_lut[rem + 1];
721 }
722 }
723
724 // Prepend negative sign directly in the buffer if space allows without width
725 // padding
726 if constexpr (Radix == radix::decimal) {
727 if (is_negative && min_width <= 0) {
728 buf[--idx] = '-';
729 }
730 }
731
732 const size_t digits_len = buf.size() - idx;
733
734 if (min_width > 0) {
735 const size_t total_needed = digits_len + (is_negative ? size_t{1} : size_t{0});
736 if (is_negative) {
737 out.put('-');
738 }
739 if (static_cast<size_t>(min_width) > total_needed) {
740 for (size_t i = 0; i < static_cast<size_t>(min_width) - total_needed; ++i) {
741 out.put('0');
742 }
743 }
744 }
745
746 out.write(microfmt::string_view(&buf[idx], digits_len));
747}
749
750// Dedicated fast path for base-2 formatting. Unlike format_integer_core, this
751// avoids runtime division/modulo (radix is a compile-time power of two here,
752// so a shift + mask suffices) and needs no intermediate buffer at all: binary
753// digits are the only base representable in constant time in MSB-first
754// (left-to-right) order, so each bit is written straight to the sink as it is
755// computed, without staging assembled/reversed digits in a local array first.
756RELOCO_ALWAYS_INLINE
757inline void format_binary(const sink &out, uint64_t val, int min_width = 0) noexcept {
758 // Number of significant bits (at least 1, so that val == 0 still emits "0").
759 int bits = 1;
760 for (uint64_t v = val; (v >>= 1) != 0;) {
761 ++bits;
762 }
763
764 for (int i = bits; i < min_width; ++i) {
765 out.put('0');
766 }
767 for (int i = bits - 1; i >= 0; --i) {
768 out.put(static_cast<char>('0' + ((val >> i) & 1U)));
769 }
770}
771
772// Dispatches to the compile-time-selected algorithm for Radix: format_binary
773// for radix::binary (shift/mask, no division), or format_integer_core
774// otherwise (decimal fast-path / hex nibble extraction).
775template <detail::radix Radix>
776RELOCO_ALWAYS_INLINE inline void format_unsigned(const sink &out, uint64_t val, [[maybe_unused]] bool uppercase,
777 int min_width = 0) noexcept {
778 if constexpr (Radix == radix::binary) {
779 format_binary(out, val, min_width);
780 } else {
781 format_integer_core<Radix>(out, val, false, uppercase, min_width);
782 }
783}
784
785// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
786RELOCO_BEGIN_UNSAFE_CODE
787RELOCO_ALWAYS_INLINE
788inline void format_signed(const sink &out, int64_t val, int min_width = 0) noexcept {
789 if (val < 0) {
790 // Safe conversion for INT64_MIN (-9223372036854775808)
791 const uint64_t mag = static_cast<uint64_t>(-(val + 1)) + 1ULL;
792 format_integer_core<radix::decimal>(out, mag, true, false, min_width);
793 } else {
794 format_integer_core<radix::decimal>(out, static_cast<uint64_t>(val), false, false, min_width);
795 }
796}
798
799// ============================================================================
800// Shared Debug-Escaping Helper (Rust `{:?}` Debug Flag / `escaped_view`)
801// ============================================================================
802//
803// Writes one character the way Rust's `Debug` impl for `str`/`char` escapes
804// it: recognized control characters as their backslash form, `quote_char`
805// itself escaped only when `escape_quote` is set (so a caller quoting with
806// `'"'` does not also escape an embedded `'\''`, and vice versa), any other
807// non-printable byte as `\xHH`, everything else written verbatim. Shared by
808// `formatter<microfmt::string_view>`/`formatter<char>`'s own `{:?}` support
809// below and `formatters/escaped.hpp`'s fuller, explicitly-invoked
810// `escaped()` adapter, so both stay byte-for-byte consistent.
811RELOCO_ALWAYS_INLINE inline void write_escaped_char(const sink &out, char ch, char quote_char,
812 bool escape_quote) noexcept {
813 const uint8_t b = static_cast<uint8_t>(ch);
814 switch (b) {
815 case '\0':
816 out.write("\\0");
817 return;
818 case '\a':
819 out.write("\\a");
820 return;
821 case '\b':
822 out.write("\\b");
823 return;
824 case '\t':
825 out.write("\\t");
826 return;
827 case '\n':
828 out.write("\\n");
829 return;
830 case '\v':
831 out.write("\\v");
832 return;
833 case '\f':
834 out.write("\\f");
835 return;
836 case '\r':
837 out.write("\\r");
838 return;
839 case '\\':
840 out.write("\\\\");
841 return;
842 default:
843 break;
844 }
845
846 if (ch == quote_char) {
847 if (escape_quote) {
848 out.put('\\');
849 }
850 out.put(quote_char);
851 return;
852 }
853
854 if (b >= 32 && b <= 126) {
855 out.put(static_cast<char>(b));
856 return;
857 }
858
859 out.write("\\x");
860 format_unsigned<radix::hex>(out, b, false, 2);
861}
862
863// Writes @p data as a `quote_char`-delimited, escaped literal (`write_escaped_char`
864// per byte, always escaping an embedded `quote_char`) -- the `{:?}` Debug
865// representation of `microfmt::string_view`/`const char *`.
866RELOCO_ALWAYS_INLINE inline void write_debug_quoted(const sink &out, microfmt::string_view data,
867 char quote_char) noexcept {
868 out.put(quote_char);
869 for (char ch : data) {
870 write_escaped_char(out, ch, quote_char, true);
871 }
872 out.put(quote_char);
873}
874
875} // namespace detail
876
877// ============================================================================
878// Format Parse Context & Formatter Customization Point
879// ============================================================================
880
882public:
883 using iterator = microfmt::string_view::const_iterator;
884 using const_iterator = microfmt::string_view::const_iterator;
885 using value_type = char;
886 using size_type = std::size_t;
887
888 constexpr explicit format_parse_context(
889 microfmt::string_view spec RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
890 : m_spec(spec) {}
891
892 // --- Core Accessors ---
893 [[nodiscard]] constexpr microfmt::string_view spec() const noexcept RELOCO_LIFETIMEBOUND { return m_spec; }
894 [[nodiscard]] constexpr bool empty() const noexcept { return m_spec.empty(); }
895 [[nodiscard]] constexpr size_type size() const noexcept { return m_spec.size(); }
896
897 // --- Iterator Interface (Required for std::format/fmtlib-style custom
898 // formatters) ---
899 [[nodiscard]] constexpr const_iterator begin() const noexcept RELOCO_LIFETIMEBOUND { return m_spec.begin(); }
900 [[nodiscard]] constexpr const_iterator end() const noexcept RELOCO_LIFETIMEBOUND { return m_spec.end(); }
901 [[nodiscard]] constexpr const_iterator cbegin() const noexcept RELOCO_LIFETIMEBOUND { return m_spec.cbegin(); }
902 [[nodiscard]] constexpr const_iterator cend() const noexcept RELOCO_LIFETIMEBOUND { return m_spec.cend(); }
903
904 // --- Element Access ---
905 [[nodiscard]] constexpr char front() const noexcept { return m_spec.empty() ? '\0' : m_spec.front(); }
906 [[nodiscard]] constexpr char back() const noexcept { return m_spec.empty() ? '\0' : m_spec.back(); }
907 [[nodiscard]] RELOCO_CHECKED constexpr char operator[](size_type idx) const noexcept {
908 return idx < m_spec.size() ? m_spec[idx] : '\0';
909 }
910
911 // --- Parsing & Cursor Advancing Utilities ---
912 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
913 // here.
914 RELOCO_BEGIN_UNSAFE_CODE
915 constexpr void advance_to(const_iterator it) noexcept {
916 if (it >= m_spec.begin() && it <= m_spec.end()) {
917 m_spec.remove_prefix(static_cast<size_type>(it - m_spec.begin()));
918 }
919 }
921
922 constexpr void remove_prefix(size_type n) noexcept { m_spec.remove_prefix(n < m_spec.size() ? n : m_spec.size()); }
923
924 constexpr char consume() noexcept {
925 if (m_spec.empty())
926 return '\0';
927 const char ch = m_spec.front();
928 m_spec.remove_prefix(1);
929 return ch;
930 }
931
932 [[nodiscard]] constexpr bool starts_with(char ch) const noexcept { return !m_spec.empty() && m_spec.front() == ch; }
933
934 [[nodiscard]] constexpr bool starts_with(microfmt::string_view prefix) const noexcept {
935#if RELOCO_CXX20
936 return m_spec.starts_with(prefix);
937#else
938 return m_spec.size() >= prefix.size() && m_spec.substr(0, prefix.size()) == prefix;
939#endif
940 }
941
942 [[nodiscard]] constexpr size_type find(char ch, size_type pos = 0) const noexcept { return m_spec.find(ch, pos); }
943
944 [[nodiscard]] constexpr microfmt::string_view
945 substr(size_type pos = 0, size_type count = microfmt::string_view::npos) const noexcept RELOCO_LIFETIMEBOUND {
946 return m_spec.substr(pos, count);
947 }
948
974 constexpr bool consume_debug_flag() noexcept {
975 if (!starts_with('?'))
976 return false;
977 remove_prefix(1);
978 return true;
979 }
980
981private:
982 microfmt::string_view m_spec;
983};
984
985template <typename T, typename Enable = void> struct formatter;
986
987// ============================================================================
988// Built-in Formatter Specializations
989// ============================================================================
990
991// Strings (const char*, string_view)
992template <> struct formatter<microfmt::string_view> {
993#ifndef MICROFMT_DISABLE_DEBUG_FLAG
994 bool debug{false};
995
996 constexpr void parse(format_parse_context &ctx) noexcept { debug = ctx.consume_debug_flag(); }
997#else
998 constexpr void parse(format_parse_context &) noexcept {}
999#endif
1000
1001 void format(microfmt::string_view val, const sink &out) const noexcept {
1002#ifndef MICROFMT_DISABLE_DEBUG_FLAG
1003 if (debug) {
1004 detail::write_debug_quoted(out, val, '"');
1005 return;
1006 }
1007#endif
1008 out.write(val);
1009 }
1010};
1011
1012// NOLINTBEGIN(reloco-banned-std-type): std::string_view interop formatter for user code
1013template <> struct formatter<std::string_view> {
1014#ifndef MICROFMT_DISABLE_DEBUG_FLAG
1015 bool debug{false};
1016
1017 constexpr void parse(format_parse_context &ctx) noexcept { debug = ctx.consume_debug_flag(); }
1018#else
1019 constexpr void parse(format_parse_context &) noexcept {}
1020#endif
1021
1022 void format(std::string_view val, const sink &out) const noexcept {
1023#ifndef MICROFMT_DISABLE_DEBUG_FLAG
1024 if (debug) {
1025 detail::write_debug_quoted(out, microfmt::string_view(val), '"');
1026 return;
1027 }
1028#endif
1029 out.write(microfmt::string_view(val));
1030 }
1031};
1032// NOLINTEND(reloco-banned-std-type)
1033
1034namespace detail {
1035
1037#ifndef MICROFMT_DISABLE_DEBUG_FLAG
1038 bool debug{false};
1039
1040 constexpr void parse(format_parse_context &ctx) noexcept { debug = ctx.consume_debug_flag(); }
1041#else
1042 constexpr void parse(format_parse_context &) noexcept {}
1043#endif
1044
1045 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
1046 // here.
1047 RELOCO_BEGIN_UNSAFE_CODE
1048 void format(const char *val, const sink &out) const noexcept {
1049 const microfmt::string_view sv = val != nullptr ? microfmt::string_view(val) : microfmt::string_view("(null)", 6);
1050#ifndef MICROFMT_DISABLE_DEBUG_FLAG
1051 if (debug) {
1052 detail::write_debug_quoted(out, sv, '"');
1053 return;
1054 }
1055#endif
1056 out.write(sv);
1057 }
1059};
1060
1061} // namespace detail
1062
1063template <> struct formatter<const char *> : detail::const_char_like {};
1064
1065// String literal / character array specialization
1066template <size_t N> struct formatter<char[N]> : detail::const_char_like {};
1067
1068template <size_t N> struct formatter<const char[N]> : detail::const_char_like {};
1069
1070template <> struct formatter<char *> : detail::const_char_like {};
1071
1072namespace detail {
1073
1074// =============================================================================
1075// Fast Backward Decimal Formatting
1076// =============================================================================
1077
1078template <typename UInt>
1079RELOCO_ALWAYS_INLINE RELOCO_UNSAFE_BUFFER_USAGE RELOCO_CONSTEXPR20 inline char *
1080format_dec_backward(char *ptr, UInt value) noexcept {
1081 // Process 2 digits at a time using the lookup table
1082 while (value >= 100) {
1083 const auto rem = static_cast<unsigned>(value % 100);
1084 value /= 100;
1085 ptr -= 2;
1086 ptr[0] = digits_lut[(unsigned)(rem * 2)];
1087 ptr[1] = digits_lut[(unsigned)(rem * 2 + 1)];
1088 }
1089
1090 // Handle remaining 1 or 2 digits
1091 if (value < 10) {
1092 *--ptr = static_cast<char>('0' + value);
1093 } else {
1094 ptr -= 2;
1095 ptr[0] = digits_lut[(unsigned)(value * 2)];
1096 ptr[1] = digits_lut[(unsigned)(value * 2 + 1)];
1097 }
1098 return ptr;
1099}
1100
1101// =============================================================================
1102// Fast Backward Hexadecimal Formatting
1103// =============================================================================
1104
1105template <typename UInt>
1106RELOCO_UNSAFE_BUFFER_USAGE RELOCO_ALWAYS_INLINE RELOCO_CONSTEXPR20 inline char *
1107format_hex_backward(char *ptr, UInt value, bool uppercase) noexcept {
1108 const auto &lut = uppercase ? hex_digits_upper : hex_digits_lower;
1109 if (value == 0) {
1110 *--ptr = '0';
1111 return ptr;
1112 }
1113 while (value > 0) {
1114 *--ptr = lut[value & 0xF];
1115 value >>= 4;
1116 }
1117 return ptr;
1118}
1119
1120// Low-overhead emission routine with zero-padding and width handling
1121// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
1122RELOCO_BEGIN_UNSAFE_CODE
1123RELOCO_ALWAYS_INLINE
1124inline void emit_formatted_int(const sink &out, const char *digits, size_t digits_len, bool is_negative,
1125 microfmt::string_view prefix, uint8_t width, bool zero_pad) noexcept {
1126 const size_t prefix_len = prefix.size();
1127 const size_t total_content_len = digits_len + prefix_len + (is_negative ? size_t{1} : size_t{0});
1128 const size_t pad_len = (width > total_content_len) ? (width - total_content_len) : 0;
1129
1130 if (zero_pad) {
1131 // Zero-padded: [sign][prefix][zeros][digits]
1132 if (is_negative) {
1133 out.put('-');
1134 }
1135 if (!prefix.empty()) {
1136 out.write(prefix);
1137 }
1138 for (size_t i = 0; i < pad_len; ++i) {
1139 out.put('0');
1140 }
1141 } else {
1142 // Space-padded: [spaces][sign][prefix][digits]
1143 for (size_t i = 0; i < pad_len; ++i) {
1144 out.put(' ');
1145 }
1146 if (is_negative) {
1147 out.put('-');
1148 }
1149 if (!prefix.empty()) {
1150 out.write(prefix);
1151 }
1152 }
1153
1154 out.write(microfmt::string_view(digits, digits_len));
1155}
1157
1158// Integers (Signed & Unsigned)
1160 uint8_t width{0};
1161
1162 // Bitfield backing all boolean state in a single byte
1163 // C++17 clean bitfields without in-class initializers
1164 // NOLINTBEGIN(reloco-member-init): pre-C++20 bit-fields cannot have default member initializers; flags{} zeroes them
1165 struct flags_t {
1166 uint8_t alt_form : 1;
1167 uint8_t zero_pad : 1;
1168 uint8_t is_hex : 1;
1169 uint8_t uppercase : 1;
1170 } flags{}; // Zero-initializes all bit-field members to 0
1171 // NOLINTEND(reloco-member-init)
1172
1173 // Out-of-line (see microfmt.ipp). MICROFMT_API_CONSTEXPR (see
1174 // detail/compat.hpp) keeps this `constexpr` in the default header-only
1175 // build, exactly as before, but drops `constexpr` under MICROFMT_SHARED:
1176 // neither call site (format_type_thunk<T>, detail::emit_piece_by_index)
1177 // ever invokes it in a constant-expression context -- both build a
1178 // runtime format_parse_context and call this normally -- so dropping
1179 // `constexpr` there costs nothing while letting a `constexpr` (thus
1180 // implicitly `inline`) function force every MICROFMT_SHARED consumer to
1181 // still carry its own definition, defeating the build/consume split.
1183
1184#if defined(MICROFMT_SHARED)
1185 // Declared only here; MICROFMT_API (see detail/compat.hpp) makes this a
1186 // plain declaration in a MICROFMT_SHARED consumer -- no body, so there is
1187 // nothing left for the compiler to inline -- and an exported, out-of-line
1188 // definition (in microfmt.ipp, MICROFMT_SHARED_BUILD only) in the library
1189 // build. `extern template` on the generic template below was tried first
1190 // but does not work: it only suppresses *implicit out-of-line
1191 // instantiation*, not inlining, and since format() (below) is always
1192 // forced inline, the compiler still had the template's in-header body
1193 // available and simply inlined format_int_impl's full body into every
1194 // call site, duplicating the code without ever emitting a callable
1195 // "format_int_impl" symbol to extern in the first place. Ordinary
1196 // (non-template) overloads have no such loophole: overload resolution
1197 // picks these exact-match overloads over the generic template below for
1198 // int64_t/uint64_t (the only widths formatter<T>::format ever generates
1199 // for integers up to 64 bits), and since they are declaration-only here,
1200 // there is no body to inline.
1201 MICROFMT_API void format_int_impl(int64_t val, const sink &out) const noexcept;
1202 MICROFMT_API void format_int_impl(uint64_t val, const sink &out) const noexcept;
1203#endif
1204
1205 // Generic fallback, used directly for the MICROFMT_SHARED case above (via
1206 // overload resolution) and also for any other T -- e.g. a >64-bit integer
1207 // via the wide-integer fallback branch below -- which keeps instantiating
1208 // normally from this in-header definition exactly as before.
1209 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
1210 // here.
1211 RELOCO_BEGIN_UNSAFE_CODE
1212 template <typename T> void format_int_impl(T val, const sink &out) const noexcept {
1213 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
1214 constexpr size_t BUF_SIZE = (sizeof(T) <= 4) ? 12 : (sizeof(T) <= 8) ? 24 : 48;
1215 char buffer[BUF_SIZE];
1216 char *end = buffer + BUF_SIZE;
1217 char *start = end;
1218
1219 using unsigned_t = std::make_unsigned_t<T>;
1220 unsigned_t uval;
1221 bool is_negative = false;
1222
1223 if constexpr (std::is_signed_v<T>) {
1224 if (val < 0) {
1225 is_negative = true;
1226 uval = static_cast<unsigned_t>(0) - static_cast<unsigned_t>(val);
1227 } else {
1228 uval = static_cast<unsigned_t>(val);
1229 }
1230 } else {
1231 uval = val;
1232 }
1233
1234 microfmt::string_view prefix{};
1235 if (flags.is_hex != 0) {
1236 start = detail::format_hex_backward(end, uval, flags.uppercase);
1237 if (flags.alt_form != 0) {
1238 prefix = flags.uppercase != 0 ? microfmt::string_view("0X") : microfmt::string_view("0x");
1239 }
1240 } else {
1241 start = detail::format_dec_backward(end, uval);
1242 }
1243
1244 const size_t digits_len = static_cast<size_t>(end - start);
1245 detail::emit_formatted_int(out, start, digits_len, is_negative, prefix, width, flags.zero_pad != 0);
1246 RELOCO_END_UNSAFE_BUFFER_USAGE;
1247 }
1249};
1250
1251} // namespace detail
1252
1253template <typename T>
1254struct formatter<T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool> && !std::is_same_v<T, char>>>
1256 RELOCO_ALWAYS_INLINE void format(T val, const sink &out) const noexcept {
1257 if constexpr (sizeof(T) <= sizeof(uint64_t)) {
1258 // No explicit <int64_t>/<uint64_t> template argument: letting plain
1259 // overload resolution pick the exact-match overload lets the
1260 // MICROFMT_SHARED, declaration-only, non-template overloads above win
1261 // over the generic template below (a non-template exact match beats a
1262 // template exact match) without changing anything in the
1263 // non-MICROFMT_SHARED build, where only the template exists.
1264 if constexpr (std::is_signed_v<T>) {
1265 format_int_impl(static_cast<int64_t>(val), out);
1266 } else {
1267 format_int_impl(static_cast<uint64_t>(val), out);
1268 }
1269 } else {
1270 // Fallback for wide integers (> 64-bit) if enabled
1271 format_int_impl(val, out);
1272 }
1273 }
1274};
1275
1276// Characters & Booleans
1277template <> struct formatter<char> {
1278#ifndef MICROFMT_DISABLE_DEBUG_FLAG
1279 bool debug{false};
1280
1281 constexpr void parse(format_parse_context &ctx) noexcept { debug = ctx.consume_debug_flag(); }
1282#else
1283 constexpr void parse(format_parse_context &) noexcept {}
1284#endif
1285
1286 void format(char val, const sink &out) const noexcept {
1287#ifndef MICROFMT_DISABLE_DEBUG_FLAG
1288 if (debug) {
1289 out.put('\'');
1290 detail::write_escaped_char(out, val, '\'', true);
1291 out.put('\'');
1292 return;
1293 }
1294#endif
1295 out.put(val);
1296 }
1297};
1298
1299template <> struct formatter<bool> {
1300 constexpr void parse(format_parse_context &) noexcept {}
1301 void format(bool val, const sink &out) const noexcept { out.write(val ? string_view("true") : string_view("false")); }
1302};
1303
1304// Raw Pointers
1305namespace detail {
1306
1308 int width{0};
1309
1310 constexpr void parse(format_parse_context &ctx) noexcept {
1311 const microfmt::string_view s = ctx.spec();
1312 size_t p = 0;
1313 if (!s.empty() && s[p] == '0')
1314 ++p;
1315 while (p < s.size() && s[p] >= '0' && s[p] <= '9') {
1316 width = width * 10 + (s[p] - '0');
1317 ++p;
1318 }
1319 }
1320
1321 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
1322 // here.
1323 RELOCO_BEGIN_UNSAFE_CODE
1324 void do_format(const void *ptr, const sink &out) const noexcept {
1325 out.write("0x");
1326 detail::format_unsigned<detail::radix::hex>(out, reinterpret_cast<uintptr_t>(ptr), false, width);
1327 }
1329};
1330
1331} // namespace detail
1332
1333template <typename T>
1334struct formatter<T *, std::enable_if_t<!std::is_same_v<T, const char> && !std::is_same_v<T, char>>>
1336 void format(T *ptr, const sink &out) const noexcept { do_format(ptr, out); }
1337};
1338
1339template <> struct formatter<std::nullptr_t> {
1340 constexpr void parse(format_parse_context &) noexcept {}
1341 void format(std::nullptr_t, const sink &out) const noexcept { out.write("0x0"); }
1342};
1343
1344// ============================================================================
1345// Static Trampoline & Type Table Machinery
1346// ============================================================================
1347
1348using format_fn_t = void (*)(const void *val_ptr, microfmt::string_view spec, const sink &out) noexcept;
1349
1350namespace detail {
1351
1360template <typename T, typename = void> struct has_formatter : std::false_type {};
1361
1362template <typename T> struct has_formatter<T, std::void_t<decltype(sizeof(formatter<T>))>> : std::true_type {};
1363
1364template <typename T> inline constexpr bool has_formatter_v = has_formatter<T>::value;
1365
1366// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
1367RELOCO_BEGIN_UNSAFE_CODE
1368template <typename T>
1369inline void format_type_thunk(const void *val_ptr, microfmt::string_view spec, const sink &out) noexcept {
1370 // std::decay_t converts char[N] -> const char*, float[] -> float*, etc.
1371 using DecayedT = std::decay_t<T>;
1372
1373 // Resolves the erased `val_ptr` back to the value/pointer `handle`
1374 // expects, exactly as before: for a bounded array argument, that's the
1375 // decayed pointer to its first element; for anything else, the
1376 // dereferenced `DecayedT` object itself.
1377 auto with_value = [&val_ptr](auto &&handle) {
1378 if constexpr (std::is_array_v<std::remove_reference_t<T>>) {
1379 const auto *decayed_val = static_cast<const std::remove_all_extents_t<std::remove_reference_t<T>> *>(val_ptr);
1380 handle(decayed_val);
1381 } else {
1382 handle(*static_cast<const DecayedT *>(val_ptr));
1383 }
1384 };
1385
1386 // Priority for a plain `{}`/`{:spec}` placeholder: an explicit
1387 // `formatter<T>` specialization always wins (unchanged from before,
1388 // including whatever it does with `spec`); only a `T` with no
1389 // `formatter<T>` at all falls through to reloco's `Display<T>`/`Debug<T>`
1390 // customization points, in that order -- both take no spec at all,
1391 // matching Rust's `Display`/`Debug` (see reloco/fmt.hpp), so `spec` is
1392 // simply never consulted on those two paths.
1393 if constexpr (has_formatter_v<DecayedT>) {
1395 format_parse_context ctx(spec);
1396 f.parse(ctx);
1397 with_value([&f, &out](const auto &value) { f.format(value, out); });
1398 } else if constexpr (reloco::has_display_v<DecayedT>) {
1399 with_value([&out](const auto &value) { reloco::Display<DecayedT>::format(value, out); });
1400 } else if constexpr (reloco::has_debug_v<DecayedT>) {
1401 with_value([&out](const auto &value) { reloco::Debug<DecayedT>::format(value, out); });
1402 } else {
1403 // No formatter<T>, Display<T>, or Debug<T> found for T: instantiate
1404 // formatter<DecayedT> anyway so the caller gets the same
1405 // "incomplete type" compile error as before this change, naming
1406 // formatter<T> as the customization point to add.
1408 format_parse_context ctx(spec);
1409 f.parse(ctx);
1410 with_value([&f, &out](const auto &value) { f.format(value, out); });
1411 }
1412}
1414
1433template <typename T> struct element_formatter {
1434 using DecayedT = std::decay_t<T>;
1435
1436 // `formatter<DecayedT>` must stay unconditionally complete as a data
1437 // member, so fall back to this stateless stub whenever `DecayedT` has no
1438 // `formatter<T>` at all (Display<T>/Debug<T>-only case).
1440 constexpr void parse(format_parse_context &) noexcept {}
1441 };
1442
1443 std::conditional_t<has_formatter_v<DecayedT>, formatter<DecayedT>, no_formatter_stub> underlying{};
1444
1445 constexpr void parse(format_parse_context &ctx) noexcept { underlying.parse(ctx); }
1446
1447 void format(const T &value, const sink &out) const noexcept {
1448 if constexpr (has_formatter_v<DecayedT>) {
1449 underlying.format(value, out);
1450 } else if constexpr (reloco::has_display_v<DecayedT>) {
1451 reloco::Display<DecayedT>::format(value, out);
1452 } else if constexpr (reloco::has_debug_v<DecayedT>) {
1453 reloco::Debug<DecayedT>::format(value, out);
1454 } else {
1455 // No formatter<T>, Display<T>, or Debug<T> found for T: same
1456 // "incomplete type" compile error as instantiating formatter<T>
1457 // directly would have produced before element_formatter existed.
1459 f.format(value, out);
1460 }
1461 }
1462};
1463
1464#if RELOCO_CXX20
1465template <typename T> using microfmt_remove_cvref_t = std::remove_cvref_t<T>;
1466#else
1467template <typename T> using microfmt_remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
1468#endif
1469
1486template <typename T> struct fix_type {
1487 using type = T;
1488};
1489template <size_t N> struct fix_type<char[N]> {
1490 using type = char[1];
1491};
1492template <size_t N> struct fix_type<const char[N]> {
1493 using type = const char[1];
1494};
1495template <typename T> using fix_type_t = typename fix_type<T>::type;
1496
1497// C++17 string_view prefix helper
1498constexpr bool starts_with(microfmt::string_view sv, microfmt::string_view prefix) noexcept {
1499 return sv.size() >= prefix.size() && sv.compare(0, prefix.size(), prefix) == 0;
1500}
1501
1502constexpr bool starts_with(microfmt::string_view sv, char c) noexcept { return !sv.empty() && sv.front() == c; }
1503
1504template <typename... Args> struct format_type_table {
1505 // Static array living in flash / .rodata (Zero runtime RAM usage)
1506 static inline constexpr microfmt::array<format_fn_t, sizeof...(Args)> functions = {
1507 &format_type_thunk<microfmt_remove_cvref_t<Args>>...};
1508
1509 static inline constexpr span<const format_fn_t> dynamic_span{functions.data(), functions.size()};
1510};
1511
1512// Specialization for zero arguments
1513template <> struct format_type_table<> {
1514 static inline constexpr span<const format_fn_t> dynamic_span{};
1515};
1516
1517// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
1518RELOCO_BEGIN_UNSAFE_CODE
1519constexpr bool parse_positional_index(microfmt::string_view text, size_t &index) noexcept {
1520 if (text.empty()) {
1521 return false;
1522 }
1523
1524 constexpr size_t max_size = static_cast<size_t>(-1);
1525 size_t value = 0;
1526 for (const char c : text) {
1527 if (c < '0' || c > '9') {
1528 return false;
1529 }
1530
1531 const size_t digit = static_cast<size_t>(c - '0');
1532 if (value > (max_size - digit) / 10) {
1533 value = max_size;
1534 } else {
1535 value = value * 10 + digit;
1536 }
1537 }
1538
1539 index = value;
1540 return true;
1541}
1543
1544// Represents a pre-parsed action in the format string
1545struct MICROFMT_API_CLASS RELOCO_POINTER compiled_piece {
1546 microfmt::string_view literal{}; // Static literal text to emit directly
1547 microfmt::string_view spec{}; // Format specifier (e.g. ":08x")
1548 size_t arg_index{0}; // Which argument index to format
1549 bool is_arg{false}; // true = format arg, false = write literal
1550};
1551
1552template <size_t MaxPieces = 32> struct compiled_format {
1553 microfmt::array<compiled_piece, MaxPieces> pieces{};
1554 size_t count{0};
1555};
1556
1557template <size_t MaxPieces = 32>
1558constexpr compiled_format<MaxPieces> compile_format_string(microfmt::string_view str) noexcept {
1560 size_t arg_idx = 0;
1561 size_t lit_start = 0;
1562
1563 for (size_t i = 0; i < str.size(); ++i) {
1564 if (str[i] == '{') {
1565 if (i + 1 < str.size() && str[i + 1] == '{') {
1566 if (i > lit_start) {
1567 result.pieces[result.count++] = compiled_piece{str.substr(lit_start, i - lit_start), {}, 0, false};
1568 }
1569 result.pieces[result.count++] = compiled_piece{str.substr(i, 1), {}, 0, false};
1570 ++i;
1571 lit_start = i + 1;
1572 continue;
1573 }
1574
1575 // Record preceding literal if non-empty
1576 if (i > lit_start) {
1577 result.pieces[result.count++] = compiled_piece{str.substr(lit_start, i - lit_start), {}, 0, false};
1578 }
1579
1580 // Find matching '}'
1581 size_t end = i + 1;
1582 while (end < str.size() && str[end] != '}') {
1583 ++end;
1584 }
1585
1586 const microfmt::string_view replacement = str.substr(i + 1, end - (i + 1));
1587 const size_t colon_pos = replacement.find(':');
1588 const microfmt::string_view index =
1589 colon_pos == microfmt::string_view::npos ? replacement : replacement.substr(0, colon_pos);
1590 const microfmt::string_view spec =
1591 colon_pos == microfmt::string_view::npos ? microfmt::string_view{} : replacement.substr(colon_pos + 1);
1592 size_t piece_arg_index = arg_idx;
1593 if (!parse_positional_index(index, piece_arg_index)) {
1594 ++arg_idx;
1595 }
1596 result.pieces[result.count++] = compiled_piece{{}, spec, piece_arg_index, true};
1597
1598 i = end;
1599 lit_start = i + 1;
1600 } else if (str[i] == '}') {
1601 if (i + 1 < str.size() && str[i + 1] == '}') {
1602 if (i > lit_start) {
1603 result.pieces[result.count++] = compiled_piece{str.substr(lit_start, i - lit_start), {}, 0, false};
1604 }
1605 result.pieces[result.count++] = compiled_piece{str.substr(i, 1), {}, 0, false};
1606 ++i;
1607 lit_start = i + 1;
1608 continue;
1609 }
1610 }
1611 }
1612
1613 // Trailing literal
1614 if (lit_start < str.size()) {
1615 result.pieces[result.count++] = compiled_piece{str.substr(lit_start), {}, 0, false};
1616 }
1617
1618 return result;
1619}
1620
1621// Helper to extract the N-th argument from a parameter pack
1622// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
1623RELOCO_BEGIN_UNSAFE_CODE
1624template <size_t TargetIdx, size_t CurIdx, typename T, typename... Rest>
1625RELOCO_ALWAYS_INLINE inline const void *get_arg_by_index(const T &first, const Rest &...rest) noexcept {
1626 if constexpr (TargetIdx == CurIdx) {
1627 return static_cast<const void *>(&first);
1628 } else {
1629 return get_arg_by_index<TargetIdx, CurIdx + 1>(rest...);
1630 }
1631}
1633
1634template <typename StrProvider> struct compiled_string_storage {
1635 static constexpr auto source = StrProvider::get();
1636 static constexpr auto compiled = compile_format_string<source.size() + 1>(source);
1637};
1638
1639// Formats a single compiled piece with zero runtime indirect thunks
1640template <typename StrProvider, size_t PieceIdx, typename... Args>
1641RELOCO_ALWAYS_INLINE inline void emit_piece_by_index(const sink &out,
1642 const std::tuple<const Args &...> &arg_tuple) noexcept {
1643 constexpr auto &piece = compiled_string_storage<StrProvider>::compiled.pieces[PieceIdx];
1644
1645 if constexpr (!piece.is_arg) {
1646 out.write(piece.literal);
1647 } else {
1648 // Select the argument reference from the tuple
1649 const auto &arg = std::get<piece.arg_index>(arg_tuple);
1650 using arg_t = microfmt_remove_cvref_t<decltype(arg)>;
1651
1653 format_parse_context ctx(piece.spec);
1654 fmt.parse(ctx);
1655 fmt.format(arg, out);
1656 }
1657}
1658
1659template <typename StrProvider, typename... Args, size_t... Is>
1660RELOCO_ALWAYS_INLINE inline void unrolled_format_impl(const sink &out, std::index_sequence<Is...>,
1661 const Args &...args) noexcept {
1662 auto arg_tuple = std::forward_as_tuple(args...);
1663 (void)arg_tuple;
1664 (emit_piece_by_index<StrProvider, Is>(out, arg_tuple), ...);
1665}
1666
1667} // namespace detail
1668
1669// ============================================================================
1670// as_debug(): Forced Debug-Priority Formatting
1671// ============================================================================
1672
1673namespace detail {
1674
1680template <typename T> struct RELOCO_POINTER debug_view {
1681 const T &value;
1682};
1683
1684} // namespace detail
1685
1702template <typename T> [[nodiscard]] constexpr auto as_debug(const T &value RELOCO_LIFETIMEBOUND) noexcept {
1703 return detail::debug_view<T>{value};
1704}
1705
1706// Rejects rvalue/temporary bindings (a plain `template <typename T> auto
1707// as_debug(T &&)` would also match lvalues -- T deducing to `T &` there --
1708// making it ambiguous with the overload above instead of rvalue-exclusive;
1709// same `enable_if_t<!is_lvalue_reference_v<T>>` guard as
1710// reloco::value_ref's own rvalue-rejecting constructor).
1711template <typename T, std::enable_if_t<!std::is_lvalue_reference_v<T>, int> = 0>
1712[[nodiscard]] constexpr auto as_debug(T &&) noexcept = delete;
1713
1714template <typename T> struct formatter<detail::debug_view<T>> {
1715 using DecayedT = std::decay_t<T>;
1716
1717 // Only ever meaningfully used (parsed/formatted) when neither `Debug<T>`
1718 // nor `Display<T>` exists, so `formatter<DecayedT>` falls back to a
1719 // stateless stub -- which must still be a complete type, since it is an
1720 // unconditional data member -- rather than requiring `formatter<DecayedT>`
1721 // itself to be complete/instantiable in the `Debug<T>`/`Display<T>` cases.
1722 struct no_formatter_stub {
1723 constexpr void parse(format_parse_context &) noexcept {}
1724 };
1725
1726 std::conditional_t<!reloco::has_debug_v<DecayedT> && detail::has_formatter_v<DecayedT>, formatter<DecayedT>,
1727 no_formatter_stub>
1728 underlying{};
1729
1730 constexpr void parse(format_parse_context &ctx) noexcept { underlying.parse(ctx); }
1731
1732 void format(const detail::debug_view<T> &view, const sink &out) const noexcept {
1733 if constexpr (reloco::has_debug_v<DecayedT>) {
1734 reloco::Debug<DecayedT>::format(view.value, out);
1735 } else if constexpr (detail::has_formatter_v<DecayedT>) {
1736 underlying.format(view.value, out);
1737 } else if constexpr (reloco::has_display_v<DecayedT>) {
1738 reloco::Display<DecayedT>::format(view.value, out);
1739 } else {
1740 static_assert(reloco::has_debug_v<DecayedT> || detail::has_formatter_v<DecayedT> ||
1741 reloco::has_display_v<DecayedT>,
1742 "microfmt::as_debug(value): no Debug<T>, formatter<T>, or Display<T> found for T");
1743 }
1744 }
1745};
1746
1747// ============================================================================
1748// Core Execution Loop
1749// ============================================================================
1750
1751// Declared here (decorated with MICROFMT_API, see detail/compat.hpp) so
1752// every microfmt header sees a consistent signature regardless of build
1753// mode; the body itself lives in microfmt.ipp, included below.
1754MICROFMT_API void vformat_to(const sink &out, const microfmt::string_view fmt, const span<const void *const> arg_ptrs,
1755 const span<const format_fn_t> arg_fns) noexcept;
1756
1757#if MICROFMT_SHARED_PROVIDE_DEFINITIONS
1758#include "microfmt.ipp"
1759#endif
1760
1761// ============================================================================
1762// Public Entry Points
1763// ============================================================================
1764
1765template <typename... Args>
1766RELOCO_CONSTEXPR20 inline void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept {
1767 if constexpr (sizeof...(Args) == 0) {
1768 vformat_to(out, fmt, {}, {});
1769 } else {
1770 // NOLINTNEXTLINE(reloco-no-c-array): fixed-size buffer/table for span APIs
1771 const void *const arg_ptrs[] = {static_cast<const void *>(&args)...};
1772 constexpr auto thunks = detail::format_type_table<detail::fix_type_t<Args>...>::dynamic_span;
1773
1774 vformat_to(out, fmt, span<const void *const>(arg_ptrs, sizeof...(Args)), thunks);
1775 }
1776}
1777
1778template <typename OutputIt, typename... Args>
1779RELOCO_CONSTEXPR20 inline OutputIt format_to(OutputIt it, microfmt::string_view fmt, const Args &...args) noexcept {
1780 iterator_sink<OutputIt> isink(it);
1781 format_to(isink.as_sink(), fmt, args...);
1782 return isink.current();
1783}
1784
1785template <size_t N, typename... Args>
1786[[nodiscard]] RELOCO_CONSTEXPR20 inline buffer_sink<N> format(microfmt::string_view fmt, const Args &...args) noexcept {
1787 buffer_sink<N> buf;
1788 format_to(buf.as_sink(), fmt, args...);
1789 return buf;
1790}
1791
1792template <typename Provider> struct compile_string_holder {
1793 using provider_type = Provider;
1794
1795 [[nodiscard]] constexpr microfmt::string_view get() const & noexcept RELOCO_LIFETIMEBOUND { return Provider::get(); }
1796
1797 [[nodiscard]] constexpr microfmt::string_view get() const && = delete;
1798};
1799
1800// Creates a unique static provider for C++17 compatibility
1801#define MICROFMT_STRING(s) \
1802 ([] { \
1803 struct str_provider { \
1804 static constexpr microfmt::string_view get() noexcept { return microfmt::string_view{s, sizeof(s) - 1}; } \
1805 }; \
1806 return ::microfmt::compile_string_holder<str_provider>{}; \
1807 }())
1808
1809// Compile-time unrolled overload (Zero stack arg_ptrs, zero indirect thunks)
1810//
1811// Only unrolls per-call-site in the default header-only build. Under
1812// MICROFMT_SHARED this degrades to the ordinary runtime-format-string
1813// overload above instead: the per-call-site unrolled expansion is exactly
1814// the kind of cost that keeps duplicating across every .so shard that
1815// #includes microfmt (unlike vformat_to/format_int_impl, it is inherently
1816// one instantiation per unique call site, not per Args pack, so it cannot
1817// be shared the same way even within a single .so -- see
1818// tools/codesize/testbed/README.md), while the runtime overload shares one
1819// MICROFMT_SHARED vformat_to definition across every consumer. The
1820// trade-off: an out-of-range `{N}` argument reference is a hard compile
1821// error via `std::get` in the unrolled path, but only the usual
1822// runtime "{MISSING}" fallback once degraded.
1823template <typename StrProvider, typename... Args>
1824RELOCO_CONSTEXPR20 inline void format_to(const sink &out, compile_string_holder<StrProvider>,
1825 const Args &...args) noexcept {
1826#if defined(MICROFMT_SHARED)
1827 format_to(out, StrProvider::get(), args...);
1828#else
1829 constexpr size_t num_pieces = detail::compiled_string_storage<StrProvider>::compiled.count;
1830
1831 detail::unrolled_format_impl<StrProvider>(out, std::make_index_sequence<num_pieces>{}, args...);
1832#endif
1833}
1834
1835// Compile-time overload
1836template <typename OutputIt, typename StrProvider, typename... Args>
1837RELOCO_CONSTEXPR20 inline OutputIt format_to(OutputIt it, compile_string_holder<StrProvider> fmt,
1838 const Args &...args) noexcept {
1839 iterator_sink<OutputIt> isink(it);
1840 format_to(isink.as_sink(), fmt, args...);
1841 return isink.current();
1842}
1843
1844// Compile-time overload
1845template <size_t N, typename StrProvider, typename... Args>
1846[[nodiscard]] RELOCO_CONSTEXPR20 inline buffer_sink<N> format(compile_string_holder<StrProvider> fmt,
1847 const Args &...args) noexcept {
1848 buffer_sink<N> buf;
1849 format_to(buf.as_sink(), fmt, args...);
1850 return buf;
1851}
1852
1863template <typename TargetContainer, typename... Args>
1864[[nodiscard]] inline TargetContainer
1865// NOLINTNEXTLINE(reloco-noexcept): std container allocation may throw (std-interop contract)
1866format_as(string_view fmt, const Args &...args) {
1867 TargetContainer result;
1868 format_to(std::back_inserter(result), fmt, args...);
1869 return result;
1870}
1871
1876template <typename TargetContainer, typename StrProvider, typename... Args>
1877[[nodiscard]] inline TargetContainer
1878// NOLINTNEXTLINE(reloco-noexcept): std container allocation may throw (std-interop contract)
1880 TargetContainer result;
1881 format_to(std::back_inserter(result), fmt, args...);
1882 return result;
1883}
1884
1891struct MICROFMT_API_CLASS RELOCO_POINTER format_args {
1892 span<const void *const> ptrs;
1893 span<const format_fn_t> fns;
1894};
1895
1896namespace detail {
1897
1901template <typename... Args> struct RELOCO_POINTER format_args_store {
1902 // Use size 1 for empty packs to avoid zero-length array compiler errors
1903 const void *const ptrs[sizeof...(Args) == 0 ? 1 : sizeof...(Args)];
1904
1905 // Captures pointers to `args`; the constructed object (and thus any
1906 // temporaries bound to `args`) must outlive its use, hence RELOCO_LIFETIMEBOUND.
1907 RELOCO_CONSTEXPR20 explicit format_args_store(const Args &...args RELOCO_LIFETIMEBOUND) noexcept
1908 : ptrs{static_cast<const void *>(&args)...} {}
1909
1910 [[nodiscard]] RELOCO_CONSTEXPR20 format_args view() const noexcept RELOCO_LIFETIMEBOUND {
1911 if constexpr (sizeof...(Args) == 0) {
1912 return {span<const void *const>{}, span<const format_fn_t>{}};
1913 } else {
1914 return {span<const void *const>(ptrs, sizeof...(Args)),
1916 }
1917 }
1918
1919 // Implicit conversion makes passing to vformat_to seamless
1920 // NOLINTNEXTLINE(reloco-explicit-conversion): implicit by design so the store passes straight to vformat_to
1921 RELOCO_CONSTEXPR20 operator format_args() const noexcept RELOCO_LIFETIMEBOUND { return view(); }
1922};
1923
1924// Zero-argument specialization
1925template <> struct RELOCO_POINTER format_args_store<> {
1926 RELOCO_CONSTEXPR20 explicit format_args_store() noexcept = default;
1927
1928 [[nodiscard]] RELOCO_CONSTEXPR20 format_args view() const noexcept {
1929 return {span<const void *const>{}, span<const format_fn_t>{}};
1930 }
1931
1932 // NOLINTNEXTLINE(reloco-explicit-conversion): implicit by design so the store passes straight to vformat_to
1933 RELOCO_CONSTEXPR20 operator format_args() const noexcept { return view(); }
1934};
1935
1936} // namespace detail
1937
1943template <typename... Args>
1944[[nodiscard]] RELOCO_CONSTEXPR20 auto make_format_args(const Args &...args RELOCO_LIFETIMEBOUND) noexcept {
1945 return detail::format_args_store<Args...>(args...);
1946}
1947
1958inline void vformat_args_to(const sink &out, microfmt::string_view fmt, format_args args) noexcept {
1959 // Delegate to the internal core implementation that handles the unsafe buffer parsing
1960 vformat_to(out, fmt, args.ptrs, args.fns);
1961}
1962
1973template <typename TargetContainer>
1974[[nodiscard]] inline TargetContainer
1975// NOLINTNEXTLINE(reloco-noexcept): std container allocation may throw (std-interop contract)
1976vformat_args_as(microfmt::string_view fmt, format_args args) {
1977 TargetContainer result;
1978 auto it = std::back_inserter(result);
1979 iterator_sink<decltype(it)> isink(it);
1980 vformat_args_to(isink.as_sink(), fmt, args);
1981 return result;
1982}
1983
1984} // namespace microfmt
Re-export of reloco's checked-precondition facility.
Output sink that stores formatted characters in an internal fixed-size stack/inline buffer.
Definition microfmt.hpp:185
constexpr std::size_t available() const noexcept
Returns the remaining unwritten capacity in the buffer.
Definition microfmt.hpp:270
constexpr buffer_sink & operator=(buffer_sink &&other) noexcept
Definition microfmt.hpp:218
constexpr std::size_t capacity() const noexcept
Returns the maximum capacity of the buffer.
Definition microfmt.hpp:263
constexpr buffer_sink & operator=(const buffer_sink &other) noexcept
Definition microfmt.hpp:208
constexpr span< const char > as_span() const &&=delete
constexpr buffer_sink(buffer_sink &&other) noexcept
Definition microfmt.hpp:201
constexpr span< const char > as_span() const &noexcept RELOCO_LIFETIMEBOUND
Returns a microfmt::span view over the written portion of the buffer.
Definition microfmt.hpp:278
RELOCO_REINITIALIZES constexpr void reset() noexcept
Resets the write offset back to the beginning of the buffer.
Definition microfmt.hpp:312
constexpr std::size_t size() const noexcept
Returns the total number of characters written so far.
Definition microfmt.hpp:256
constexpr microfmt::string_view view() const &noexcept RELOCO_LIFETIMEBOUND
Returns a string view over the characters written so far.
Definition microfmt.hpp:245
RELOCO_BEGIN_UNSAFE_BUFFER_USAGE constexpr buffer_sink(const buffer_sink &other) noexcept
Definition microfmt.hpp:194
constexpr buffer_sink() noexcept
Constructs an empty buffer sink.
Definition microfmt.hpp:190
RELOCO_END_UNSAFE_BUFFER_USAGE RELOCO_CONSTEXPR20 sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to this instance.
Definition microfmt.hpp:236
constexpr microfmt::string_view view() const &&=delete
Output sink that guarantees a null-terminated string within a fixed-size buffer.
Definition microfmt.hpp:486
constexpr c_string_sink(c_string_sink &&other) noexcept
Definition microfmt.hpp:505
constexpr c_string_sink & operator=(const c_string_sink &other) noexcept
Definition microfmt.hpp:513
RELOCO_END_UNSAFE_BUFFER_USAGE sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to this instance.
Definition microfmt.hpp:543
RELOCO_END_UNSAFE_CODE constexpr const char * c_str() const &&=delete
constexpr microfmt::string_view view() const &&=delete
RELOCO_REINITIALIZES constexpr void reset() noexcept
Resets the sink to an empty, null-terminated state.
Definition microfmt.hpp:591
constexpr std::size_t size() const noexcept
Returns the length of the string in bytes (excluding null terminator).
Definition microfmt.hpp:578
RELOCO_BEGIN_UNSAFE_BUFFER_USAGE constexpr c_string_sink(const c_string_sink &other) noexcept
Definition microfmt.hpp:497
constexpr c_string_sink() noexcept
Constructs an empty, null-terminated string sink.
Definition microfmt.hpp:493
RELOCO_BEGIN_UNSAFE_CODE constexpr const char * c_str() const &noexcept RELOCO_LIFETIMEBOUND
Returns a null-terminated C string pointer.
Definition microfmt.hpp:555
static constexpr std::size_t max_size() noexcept
Returns the maximum characters the sink can store (excluding null terminator).
Definition microfmt.hpp:586
constexpr c_string_sink & operator=(c_string_sink &&other) noexcept
Definition microfmt.hpp:524
constexpr microfmt::string_view view() const &noexcept RELOCO_LIFETIMEBOUND
Returns a string view over the characters written (excluding null terminator).
Definition microfmt.hpp:566
Output sink that adapts any callable object (lambda, functor) to a sink.
Definition microfmt.hpp:607
sink as_sink() noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to the wrapped callable.
Definition microfmt.hpp:623
constexpr callback_sink(Callable &fn RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Constructs a callback sink wrapping a reference to a callable.
Definition microfmt.hpp:614
Output sink that counts total formatted bytes without storing characters.
Definition microfmt.hpp:384
constexpr counting_sink() noexcept=default
Constructs a counting sink with a zero byte count.
RELOCO_REINITIALIZES constexpr void reset() noexcept
Resets the internal counter to zero.
Definition microfmt.hpp:418
RELOCO_END_UNSAFE_CODE constexpr std::size_t count() const noexcept
Returns the total number of bytes written to the sink.
Definition microfmt.hpp:413
Definition microfmt.hpp:881
constexpr bool consume_debug_flag() noexcept
Detects and consumes a leading ? – Rust's {:?} Debug flag, as opposed to {}'s Display – returning tru...
Definition microfmt.hpp:974
constexpr const_iterator end() const noexcept RELOCO_LIFETIMEBOUND
Definition microfmt.hpp:900
RELOCO_END_UNSAFE_CODE constexpr void remove_prefix(size_type n) noexcept
Definition microfmt.hpp:922
constexpr size_type find(char ch, size_type pos=0) const noexcept
Definition microfmt.hpp:942
constexpr microfmt::string_view spec() const noexcept RELOCO_LIFETIMEBOUND
Definition microfmt.hpp:893
constexpr size_type size() const noexcept
Definition microfmt.hpp:895
constexpr microfmt::string_view substr(size_type pos=0, size_type count=microfmt::string_view::npos) const noexcept RELOCO_LIFETIMEBOUND
Definition microfmt.hpp:945
constexpr const_iterator cend() const noexcept RELOCO_LIFETIMEBOUND
Definition microfmt.hpp:902
constexpr bool empty() const noexcept
Definition microfmt.hpp:894
std::size_t size_type
Definition microfmt.hpp:886
microfmt::string_view::const_iterator iterator
Definition microfmt.hpp:883
RELOCO_BEGIN_UNSAFE_CODE constexpr void advance_to(const_iterator it) noexcept
Definition microfmt.hpp:915
constexpr bool starts_with(char ch) const noexcept
Definition microfmt.hpp:932
constexpr char back() const noexcept
Definition microfmt.hpp:906
constexpr bool starts_with(microfmt::string_view prefix) const noexcept
Definition microfmt.hpp:934
constexpr char consume() noexcept
Definition microfmt.hpp:924
constexpr const_iterator cbegin() const noexcept RELOCO_LIFETIMEBOUND
Definition microfmt.hpp:901
microfmt::string_view::const_iterator const_iterator
Definition microfmt.hpp:884
constexpr char front() const noexcept
Definition microfmt.hpp:905
RELOCO_CHECKED constexpr char operator[](size_type idx) const noexcept
Definition microfmt.hpp:907
constexpr format_parse_context(microfmt::string_view spec RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Definition microfmt.hpp:888
char value_type
Definition microfmt.hpp:885
constexpr const_iterator begin() const noexcept RELOCO_LIFETIMEBOUND
Definition microfmt.hpp:899
Output sink adapter for arbitrary output iterators or raw pointers.
Definition microfmt.hpp:327
constexpr iterator_sink(OutputIt it) noexcept
Constructs an iterator sink wrapping the target output iterator.
Definition microfmt.hpp:334
RELOCO_END_UNSAFE_CODE constexpr OutputIt current() const noexcept
Returns the current state of the output iterator.
Definition microfmt.hpp:371
RELOCO_BEGIN_UNSAFE_CODE sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to this instance.
Definition microfmt.hpp:345
Null/Discard sink that discards all formatted output.
Definition microfmt.hpp:430
static constexpr sink as_sink() noexcept
Returns a shared, stateless type-erased sink instance that drops writes.
Definition microfmt.hpp:438
Output sink that writes formatted characters into a fixed-size contiguous memory buffer.
Definition microfmt.hpp:60
constexpr span_sink(span< char > buf RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Constructs a span sink over a microfmt::span of character storage.
Definition microfmt.hpp:68
RELOCO_REINITIALIZES constexpr void reset() noexcept
Resets the write offset back to the beginning of the buffer.
Definition microfmt.hpp:135
constexpr size_t available() const noexcept
Returns the remaining unwritten capacity in the buffer.
Definition microfmt.hpp:128
sink as_sink() noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to this instance.
Definition microfmt.hpp:93
constexpr size_t size() const noexcept
Returns the total number of characters written so far.
Definition microfmt.hpp:121
constexpr microfmt::string_view view() const noexcept RELOCO_LIFETIMEBOUND
Returns a string view over the characters written so far.
Definition microfmt.hpp:112
Compiler, language-version, and optional-library feature detection.
#define MICROFMT_API_CLASS
Definition compat.hpp:181
#define MICROFMT_API
Definition compat.hpp:135
#define MICROFMT_API_CONSTEXPR
Definition compat.hpp:163
Definition fmt.hpp:17
constexpr style reset
Definition ansi.hpp:79
std::remove_cv_t< std::remove_reference_t< T > > microfmt_remove_cvref_t
Definition microfmt.hpp:1467
constexpr bool starts_with(microfmt::string_view sv, microfmt::string_view prefix) noexcept
Definition microfmt.hpp:1498
RELOCO_ALWAYS_INLINE void unrolled_format_impl(const sink &out, std::index_sequence< Is... >, const Args &...args) noexcept
Definition microfmt.hpp:1660
constexpr bool has_formatter_v
Definition microfmt.hpp:1364
RELOCO_BEGIN_UNSAFE_CODE RELOCO_ALWAYS_INLINE void emit_formatted_int(const sink &out, const char *digits, size_t digits_len, bool is_negative, microfmt::string_view prefix, uint8_t width, bool zero_pad) noexcept
Definition microfmt.hpp:1124
RELOCO_ALWAYS_INLINE void emit_piece_by_index(const sink &out, const std::tuple< const Args &... > &arg_tuple) noexcept
Definition microfmt.hpp:1641
RELOCO_BEGIN_UNSAFE_CODE constexpr bool parse_positional_index(microfmt::string_view text, size_t &index) noexcept
Definition microfmt.hpp:1519
constexpr compiled_format< MaxPieces > compile_format_string(microfmt::string_view str) noexcept
Definition microfmt.hpp:1558
RELOCO_ALWAYS_INLINE RELOCO_UNSAFE_BUFFER_USAGE RELOCO_CONSTEXPR20 char * format_dec_backward(char *ptr, UInt value) noexcept
Definition microfmt.hpp:1080
RELOCO_UNSAFE_BUFFER_USAGE RELOCO_ALWAYS_INLINE RELOCO_CONSTEXPR20 char * format_hex_backward(char *ptr, UInt value, bool uppercase) noexcept
Definition microfmt.hpp:1107
RELOCO_BEGIN_UNSAFE_CODE void format_type_thunk(const void *val_ptr, microfmt::string_view spec, const sink &out) noexcept
Definition microfmt.hpp:1369
RELOCO_BEGIN_UNSAFE_CODE RELOCO_ALWAYS_INLINE const void * get_arg_by_index(const T &first, const Rest &...rest) noexcept
Definition microfmt.hpp:1625
typename fix_type< T >::type fix_type_t
Definition microfmt.hpp:1495
Definition assert.hpp:19
TargetContainer vformat_args_as(microfmt::string_view fmt, format_args args)
Formats type-erased arguments into a newly constructed container (e.g., std::string).
Definition microfmt.hpp:1976
constexpr hex_view< T > hex(T val, int width=0, bool prefix=false, bool uppercase=false) noexcept
Definition format_helpers.hpp:29
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
RELOCO_CONSTEXPR20 auto make_format_args(const Args &...args RELOCO_LIFETIMEBOUND) noexcept
Captures arguments into a type-erased storage object.
Definition microfmt.hpp:1944
void(*)(const void *val_ptr, microfmt::string_view spec, const sink &out) noexcept format_fn_t
Definition microfmt.hpp:1348
constexpr bitfield_view bits(uint32_t raw_val, span< const bit_field > fields, bool show_raw_hex=true, microfmt::string_view sep=" | ") noexcept
Definition bitfield.hpp:36
constexpr callback_sink< Callable > make_callback_sink(Callable &fn RELOCO_LIFETIMEBOUND) noexcept
Helper factory function to construct a callback_sink with template deduction.
Definition microfmt.hpp:643
MICROFMT_API void vformat_to(const sink &out, const microfmt::string_view fmt, const span< const void *const > arg_ptrs, const span< const format_fn_t > arg_fns) noexcept
void vformat_args_to(const sink &out, microfmt::string_view fmt, format_args args) noexcept
Type-erased formatting engine entry point.
Definition microfmt.hpp:1958
RELOCO_CONSTEXPR20 buffer_sink< N > format(microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1786
constexpr auto as_debug(const T &value RELOCO_LIFETIMEBOUND) noexcept
Wraps value so it always renders through reloco's Debug<T> customization point when one exists,...
Definition microfmt.hpp:1702
TargetContainer format_as(string_view fmt, const Args &...args)
Formats arguments into a newly constructed container (e.g., std::string) using a runtime format strin...
Definition microfmt.hpp:1866
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Single re-export of every `reloco` type and annotation microfmt builds on.
Definition microfmt.hpp:1792
Provider provider_type
Definition microfmt.hpp:1793
constexpr microfmt::string_view get() const &noexcept RELOCO_LIFETIMEBOUND
Definition microfmt.hpp:1795
constexpr microfmt::string_view get() const &&=delete
Definition microfmt.hpp:445
static RELOCO_CONSTEXPR20 void write_thunk(void *ctx, microfmt::string_view sv) noexcept
Definition microfmt.hpp:460
c_string_sink_base & operator=(c_string_sink_base &&)=delete
std::size_t m_pos
Definition microfmt.hpp:448
constexpr c_string_sink_base(char *ptr RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS, size_t max, size_t pos) noexcept
Definition microfmt.hpp:450
c_string_sink_base(c_string_sink_base &&)=delete
char * m_data
Definition microfmt.hpp:446
c_string_sink_base & operator=(const c_string_sink_base &)=delete
c_string_sink_base(const c_string_sink_base &)=delete
Definition microfmt.hpp:1552
size_t count
Definition microfmt.hpp:1554
microfmt::array< compiled_piece, MaxPieces > pieces
Definition microfmt.hpp:1553
Definition microfmt.hpp:1545
Definition microfmt.hpp:1634
static constexpr auto source
Definition microfmt.hpp:1635
static constexpr auto compiled
Definition microfmt.hpp:1636
Definition microfmt.hpp:1036
RELOCO_BEGIN_UNSAFE_CODE void format(const char *val, const sink &out) const noexcept
Definition microfmt.hpp:1048
constexpr void parse(format_parse_context &ctx) noexcept
Definition microfmt.hpp:1040
Non-owning wrapper produced by as_debug, carrying no state of its own beyond a reference to the wrapp...
Definition microfmt.hpp:1680
const T & value
Definition microfmt.hpp:1681
constexpr void parse(format_parse_context &) noexcept
Definition microfmt.hpp:1440
Drop-in replacement for a formatter<T> underlying_formatter; member used by container/wrapper formatt...
Definition microfmt.hpp:1433
constexpr void parse(format_parse_context &ctx) noexcept
Definition microfmt.hpp:1445
std::conditional_t< has_formatter_v< DecayedT >, formatter< DecayedT >, no_formatter_stub > underlying
Definition microfmt.hpp:1443
std::decay_t< T > DecayedT
Definition microfmt.hpp:1434
void format(const T &value, const sink &out) const noexcept
Definition microfmt.hpp:1447
char[1] type
Definition microfmt.hpp:1490
const char[1] type
Definition microfmt.hpp:1493
Collapses argument types to a canonical representative before they reach format_type_table,...
Definition microfmt.hpp:1486
T type
Definition microfmt.hpp:1487
RELOCO_CONSTEXPR20 format_args_store() noexcept=default
Stack storage for type-erased argument pointers.
Definition microfmt.hpp:1901
RELOCO_CONSTEXPR20 format_args_store(const Args &...args RELOCO_LIFETIMEBOUND) noexcept
Definition microfmt.hpp:1907
RELOCO_CONSTEXPR20 format_args view() const noexcept RELOCO_LIFETIMEBOUND
Definition microfmt.hpp:1910
Definition microfmt.hpp:1504
static constexpr microfmt::array< format_fn_t, sizeof...(Args)> functions
Definition microfmt.hpp:1506
static constexpr span< const format_fn_t > dynamic_span
Definition microfmt.hpp:1509
Detects whether formatter<T> has been given a definition (as opposed to just matching the template <t...
Definition microfmt.hpp:1360
uint8_t alt_form
Definition microfmt.hpp:1166
uint8_t uppercase
Definition microfmt.hpp:1169
uint8_t zero_pad
Definition microfmt.hpp:1167
uint8_t is_hex
Definition microfmt.hpp:1168
Definition microfmt.hpp:1159
MICROFMT_API_CONSTEXPR void parse(format_parse_context &ctx) noexcept
RELOCO_BEGIN_UNSAFE_CODE void format_int_impl(T val, const sink &out) const noexcept
Definition microfmt.hpp:1212
Definition microfmt.hpp:1307
RELOCO_BEGIN_UNSAFE_CODE void do_format(const void *ptr, const sink &out) const noexcept
Definition microfmt.hpp:1324
constexpr void parse(format_parse_context &ctx) noexcept
Definition microfmt.hpp:1310
Type-erased, non-owning view of formatting arguments.
Definition microfmt.hpp:1891
span< const void *const > ptrs
Definition microfmt.hpp:1892
span< const format_fn_t > fns
Definition microfmt.hpp:1893
RELOCO_ALWAYS_INLINE void format(T val, const sink &out) const noexcept
Definition microfmt.hpp:1256
void format(T *ptr, const sink &out) const noexcept
Definition microfmt.hpp:1336
void format(bool val, const sink &out) const noexcept
Definition microfmt.hpp:1301
constexpr void parse(format_parse_context &) noexcept
Definition microfmt.hpp:1300
constexpr void parse(format_parse_context &ctx) noexcept
Definition microfmt.hpp:1281
void format(char val, const sink &out) const noexcept
Definition microfmt.hpp:1286
constexpr void parse(format_parse_context &) noexcept
Definition microfmt.hpp:1723
constexpr void parse(format_parse_context &ctx) noexcept
Definition microfmt.hpp:1730
void format(const detail::debug_view< T > &view, const sink &out) const noexcept
Definition microfmt.hpp:1732
std::decay_t< T > DecayedT
Definition microfmt.hpp:1715
void format(microfmt::string_view val, const sink &out) const noexcept
Definition microfmt.hpp:1001
constexpr void parse(format_parse_context &ctx) noexcept
Definition microfmt.hpp:996
constexpr void parse(format_parse_context &) noexcept
Definition microfmt.hpp:1340
void format(std::nullptr_t, const sink &out) const noexcept
Definition microfmt.hpp:1341
constexpr void parse(format_parse_context &ctx) noexcept
Definition microfmt.hpp:1017
void format(std::string_view val, const sink &out) const noexcept
Definition microfmt.hpp:1022
Definition microfmt.hpp:985