microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
fmt.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
9#include "../microfmt.hpp"
10#include "ranges.hpp"
11#include <cstddef>
12#include <cstdio>
13#include <iterator>
14#include <string_view>
15#include <type_traits>
16
17namespace fmt {
18
19// Default stack buffer size for fmt::format(...) if no template size is
20// supplied
21inline constexpr size_t DEFAULT_FORMAT_BUFFER_SIZE = 128;
22
23// ============================================================================
24// Result & Context Types
25// ============================================================================
26
27template <typename OutputIt> struct format_to_n_result {
28 OutputIt out;
29 size_t size;
30};
31
32using string_view = microfmt::string_view;
33
34// Parse context emulating fmt::format_parse_context
36 microfmt::string_view spec;
37
38 constexpr auto begin() const & noexcept { return spec.begin(); }
39
40 constexpr auto begin() const && = delete;
41 constexpr auto end() const & noexcept { return spec.end(); }
42
43 constexpr auto end() const && = delete;
44 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
45 // here.
46 RELOCO_BEGIN_UNSAFE_CODE
47 constexpr void advance_to(microfmt::string_view::iterator it) noexcept {
48 spec.remove_prefix(static_cast<size_t>(it - spec.begin()));
49 }
50 RELOCO_END_UNSAFE_CODE
51};
52
53// ============================================================================
54// Output Iterator Adapter wrapping microfmt::sink
55// ============================================================================
56
58public:
59 using iterator_category = std::output_iterator_tag;
60 using value_type = void;
61 using difference_type = std::ptrdiff_t;
62 using pointer = void;
63 using reference = void;
64
65 constexpr explicit sink_output_iterator(const microfmt::sink &s RELOCO_LIFETIMEBOUND) noexcept : sink_(s) {}
66
67 sink_output_iterator &operator=(char c) noexcept {
68 sink_->put(c);
69 return *this;
70 }
71
72 // NOLINTNEXTLINE(reloco-checked-operator): output-iterator proxy; dereferencing returns the iterator itself
73 constexpr sink_output_iterator &operator*() noexcept { return *this; }
74 constexpr sink_output_iterator &operator++() noexcept { return *this; }
75 constexpr sink_output_iterator &operator++(int) noexcept { return *this; }
76
77private:
78 microfmt::value_ref<const microfmt::sink> sink_;
79};
80
81// Minimal emulated format_context
82class MICROFMT_API_CLASS RELOCO_POINTER format_context {
83public:
85
86 constexpr explicit format_context(iterator out) noexcept : out_(out) {}
87
88 constexpr iterator out() const & noexcept { return out_; }
89
90 constexpr iterator out() const && = delete;
91 constexpr void advance_to(iterator it) noexcept { out_ = it; }
92
93private:
94 iterator out_;
95};
96
97// Primary template for fmt::formatter
98template <typename T, typename Char = char, typename Enable = void> struct formatter;
99
100// ============================================================================
101// fmt::format_to_n
102// ============================================================================
103
104// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
105RELOCO_BEGIN_UNSAFE_CODE
106template <typename... Args>
107format_to_n_result<char *> format_to_n(char *out, size_t n, microfmt::string_view fmt_str,
108 const Args &...args) noexcept {
109 struct BoundedBufferState {
110 char *data;
111 size_t capacity;
112 size_t size;
113 } state{out, n, 0};
114
115 microfmt::sink s{&state, [](void *ctx, microfmt::string_view sv) noexcept {
116 auto *st = static_cast<BoundedBufferState *>(ctx);
117 if (st->data != nullptr && st->size < st->capacity) {
118 const size_t available = st->capacity - st->size;
119 const size_t to_copy = (sv.size() < available) ? sv.size() : available;
120 for (size_t i = 0; i < to_copy; ++i) {
121 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
122
123 st->data[st->size + i] = sv[i];
124
125 RELOCO_END_UNSAFE_BUFFER_USAGE;
126 }
127 }
128 st->size += sv.size();
129 }};
130
131 microfmt::format_to(s, fmt_str, args...);
132
133 const size_t written = (state.size < state.capacity) ? state.size : state.capacity;
134
135 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
136
137 return {out + written, state.size};
138
139 RELOCO_END_UNSAFE_BUFFER_USAGE;
140}
141RELOCO_END_UNSAFE_CODE
142
143// ============================================================================
144// fmt::format_to (Iterator / Pointer overload)
145// ============================================================================
146
147// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
148RELOCO_BEGIN_UNSAFE_CODE
149template <typename OutputIt, typename... Args,
150 std::enable_if_t<std::is_pointer_v<OutputIt> &&
151 std::is_same_v<std::remove_cv_t<std::remove_pointer_t<OutputIt>>, char>,
152 int> = 0>
153OutputIt format_to(OutputIt out, microfmt::string_view fmt_str, const Args &...args) noexcept {
154 struct PtrSinkState {
155 char *ptr;
156 } state{out};
157
158 microfmt::sink s{&state, [](void *ctx, microfmt::string_view sv) noexcept {
159 auto *st = static_cast<PtrSinkState *>(ctx);
160 for (char c : sv) {
161 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
162 *st->ptr++ = c;
163 RELOCO_END_UNSAFE_BUFFER_USAGE;
164 }
165 }};
166
167 microfmt::format_to(s, fmt_str, args...);
168 return state.ptr;
169}
170RELOCO_END_UNSAFE_CODE
171
172// ============================================================================
173// fmt::format (Stack-Bounded Return Type)
174// ============================================================================
175
176// Explicit capacity version: fmt::format<256>("...", ...)
177template <size_t Capacity, typename... Args>
178[[nodiscard]] auto format(microfmt::string_view fmt_str, const Args &...args) noexcept {
179 return microfmt::format<Capacity>(fmt_str, args...);
180}
181
182// Default capacity version: fmt::format("...", ...)
183template <typename... Args> [[nodiscard]] auto format(microfmt::string_view fmt_str, const Args &...args) noexcept {
184 return microfmt::format<DEFAULT_FORMAT_BUFFER_SIZE>(fmt_str, args...);
185}
186
187// ============================================================================
188// fmt::print & fmt::println
189// ============================================================================
190
191RELOCO_BEGIN_UNSAFE_BUFFER_USAGE
192
193// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
194RELOCO_BEGIN_UNSAFE_CODE
195inline void stdout_writer(void *, microfmt::string_view sv) noexcept { std::fwrite(sv.data(), 1, sv.size(), stdout); }
196RELOCO_END_UNSAFE_CODE
197
198// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
199RELOCO_BEGIN_UNSAFE_CODE
200inline void stderr_writer(void *, microfmt::string_view sv) noexcept { std::fwrite(sv.data(), 1, sv.size(), stderr); }
201RELOCO_END_UNSAFE_CODE
202
203RELOCO_END_UNSAFE_BUFFER_USAGE
204
205template <typename... Args> void print(microfmt::string_view fmt_str, const Args &...args) noexcept {
206 microfmt::sink term{nullptr, stdout_writer};
207 microfmt::format_to(term, fmt_str, args...);
208}
209
210// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
211RELOCO_BEGIN_UNSAFE_CODE
212template <typename... Args> void print(std::FILE *f, microfmt::string_view fmt_str, const Args &...args) noexcept {
213 microfmt::sink term{f, [](void *ctx, microfmt::string_view sv) noexcept {
214 std::fwrite(sv.data(), 1, sv.size(), static_cast<std::FILE *>(ctx));
215 }};
216 microfmt::format_to(term, fmt_str, args...);
217}
218RELOCO_END_UNSAFE_CODE
219
220template <typename... Args> void println(microfmt::string_view fmt_str, const Args &...args) noexcept {
221 microfmt::sink term{nullptr, stdout_writer};
222 microfmt::format_to(term, fmt_str, args...);
223 term.put('\n');
224}
225
226// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
227RELOCO_BEGIN_UNSAFE_CODE
228template <typename... Args> void println(std::FILE *f, microfmt::string_view fmt_str, const Args &...args) noexcept {
229 microfmt::sink term{f, [](void *ctx, microfmt::string_view sv) noexcept {
230 std::fwrite(sv.data(), 1, sv.size(), static_cast<std::FILE *>(ctx));
231 }};
232 microfmt::format_to(term, fmt_str, args...);
233 term.put('\n');
234}
235RELOCO_END_UNSAFE_CODE
236
237// ============================================================================
238// Range Adapter Bridge
239// ============================================================================
240
241template <typename Range>
242[[nodiscard]] constexpr auto join(const Range &range, microfmt::string_view delimiter) noexcept {
243 return microfmt::join(range, delimiter);
244}
245
246template <typename It, typename Sentinel>
247[[nodiscard]] constexpr auto join(It first, Sentinel last, microfmt::string_view delimiter) noexcept {
248 return microfmt::join(first, last, delimiter);
249}
250
251} // namespace fmt
252
253// ============================================================================
254// microfmt Formatter Bridge
255// ============================================================================
256
257namespace microfmt {
258
259namespace detail {
260
261// Checks if fmt::formatter<T> has been specialized
262template <typename T, typename = void> struct has_fmt_formatter : std::false_type {};
263
264template <typename T>
265struct has_fmt_formatter<
266 T, std::void_t<decltype(std::declval<fmt::formatter<T> &>().parse(std::declval<fmt::format_parse_context &>())),
267 decltype(std::declval<fmt::formatter<T> &>().format(
268 std::declval<const T &>(), std::declval<fmt::format_context &>()))>> : std::true_type {};
269
270} // namespace detail
271
272// Automatically forward to fmt::formatter<T> when available
273template <typename T> struct formatter<T, std::enable_if_t<detail::has_fmt_formatter<T>::value>> {
274 fmt::formatter<T> fmt_impl{};
275
276 constexpr void parse(format_parse_context &ctx) noexcept {
278 fmt_impl.parse(pctx);
279 }
280
281 void format(const T &val, const sink &out) const noexcept {
283 fmt::format_context fctx(it);
284 fmt_impl.format(val, fctx);
285 }
286};
287
288} // namespace microfmt
Definition fmt.hpp:82
constexpr format_context(iterator out) noexcept
Definition fmt.hpp:86
constexpr iterator out() const &&=delete
constexpr iterator out() const &noexcept
Definition fmt.hpp:88
Definition fmt.hpp:57
constexpr sink_output_iterator & operator++(int) noexcept
Definition fmt.hpp:75
constexpr sink_output_iterator & operator++() noexcept
Definition fmt.hpp:74
void pointer
Definition fmt.hpp:62
sink_output_iterator & operator=(char c) noexcept
Definition fmt.hpp:67
std::output_iterator_tag iterator_category
Definition fmt.hpp:59
constexpr sink_output_iterator & operator*() noexcept
Definition fmt.hpp:73
void reference
Definition fmt.hpp:63
std::ptrdiff_t difference_type
Definition fmt.hpp:61
constexpr sink_output_iterator(const microfmt::sink &s RELOCO_LIFETIMEBOUND) noexcept
Definition fmt.hpp:65
void value_type
Definition fmt.hpp:60
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition fmt.hpp:17
RELOCO_BEGIN_UNSAFE_CODE format_to_n_result< char * > format_to_n(char *out, size_t n, microfmt::string_view fmt_str, const Args &...args) noexcept
Definition fmt.hpp:107
RELOCO_END_UNSAFE_CODE auto format(microfmt::string_view fmt_str, const Args &...args) noexcept
Definition fmt.hpp:178
RELOCO_END_UNSAFE_CODE RELOCO_BEGIN_UNSAFE_CODE void stderr_writer(void *, microfmt::string_view sv) noexcept
Definition fmt.hpp:200
microfmt::string_view string_view
Definition fmt.hpp:32
RELOCO_BEGIN_UNSAFE_BUFFER_USAGE RELOCO_BEGIN_UNSAFE_CODE void stdout_writer(void *, microfmt::string_view sv) noexcept
Definition fmt.hpp:195
RELOCO_END_UNSAFE_CODE constexpr auto join(const Range &range, microfmt::string_view delimiter) noexcept
Definition fmt.hpp:242
constexpr size_t DEFAULT_FORMAT_BUFFER_SIZE
Definition fmt.hpp:21
RELOCO_END_UNSAFE_CODE void println(microfmt::string_view fmt_str, const Args &...args) noexcept
Definition fmt.hpp:220
RELOCO_END_UNSAFE_CODE RELOCO_END_UNSAFE_BUFFER_USAGE void print(microfmt::string_view fmt_str, const Args &...args) noexcept
Definition fmt.hpp:205
RELOCO_END_UNSAFE_CODE RELOCO_BEGIN_UNSAFE_CODE OutputIt format_to(OutputIt out, microfmt::string_view fmt_str, const Args &...args) noexcept
Definition fmt.hpp:153
Definition assert.hpp:19
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 buffer_sink< N > format(microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1786
constexpr joined_span_view< T > join(span< const T > items, microfmt::string_view delim=", ") noexcept
Definition format_helpers.hpp:239
Range and iterator-pair joining formatting views.
Definition fmt.hpp:35
microfmt::string_view spec
Definition fmt.hpp:36
constexpr auto begin() const &&=delete
constexpr auto begin() const &noexcept
Definition fmt.hpp:38
constexpr auto end() const &&=delete
Definition fmt.hpp:27
size_t size
Definition fmt.hpp:29
OutputIt out
Definition fmt.hpp:28
Definition fmt.hpp:98