microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
sink_provider.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
29#include "microfmt.hpp"
30
31#include <type_traits>
32#include <utility>
33
34namespace microfmt {
35
50template <typename Tag> struct sink_provider_traits;
51
52namespace detail {
53
54template <typename Tag, typename = void> struct has_sink_provider_flush : std::false_type {};
55
56template <typename Tag>
57struct has_sink_provider_flush<Tag, std::void_t<decltype(sink_provider_traits<Tag>::flush(
58 std::declval<value_ref<typename sink_provider_traits<Tag>::context_type>>()))>>
59 : std::true_type {};
60
61template <typename Tag, typename = void> struct has_stateless_sink_provider_flush : std::false_type {};
62
63template <typename Tag>
64struct has_stateless_sink_provider_flush<Tag, std::void_t<decltype(sink_provider_traits<Tag>::flush())>>
65 : std::true_type {};
66
67} // namespace detail
68
79public:
83 struct vtable {
91 void (*write)(void *ctx, microfmt::string_view sv) noexcept = nullptr;
96 void (*flush)(void *ctx) noexcept = nullptr;
97 };
98
102 constexpr sink_provider_ref() noexcept = default;
103
109 template <typename Tag, typename Traits = sink_provider_traits<Tag>,
110 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
111 constexpr explicit sink_provider_ref(Tag) noexcept : ctx_(nullptr), vtbl_(&s_vtbl<Tag>) {}
112
121 template <typename Tag, typename Context, typename Traits = sink_provider_traits<Tag>,
122 std::enable_if_t<!std::is_void_v<typename Traits::context_type> &&
123 std::is_convertible_v<Context *, typename Traits::context_type *>,
124 int> = 0>
125 constexpr sink_provider_ref(Tag, Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
126 : ctx_(&ctx), vtbl_(&s_vtbl<Tag>) {}
127
128 // Refuses to bind to a temporary context, which would leave ctx_ dangling
129 // the moment this constructor returns.
130 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
131 constexpr sink_provider_ref(Tag, Context &&) = delete;
132
139 void write(microfmt::string_view sv) const noexcept {
140 if (vtbl_ != nullptr && !sv.empty()) {
141 vtbl_->write(ctx_.get(), sv);
142 }
143 }
144
148 [[nodiscard]] constexpr bool can_flush() const noexcept { return vtbl_ != nullptr && vtbl_->flush != nullptr; }
149
153 void flush() const noexcept {
154 if (can_flush()) {
155 vtbl_->flush(ctx_.get());
156 }
157 }
158
162 [[nodiscard]] constexpr explicit operator bool() const noexcept { return vtbl_ != nullptr; }
163
171 [[nodiscard]] constexpr sink as_sink() const noexcept RELOCO_LIFETIMEBOUND {
172 return sink{ctx_.get(), vtbl_ != nullptr ? vtbl_->write : nullptr};
173 }
174
175private:
176 template <typename Tag> static void write_entry(void *ctx, microfmt::string_view sv) noexcept {
177 using context_type = typename sink_provider_traits<Tag>::context_type;
178 if constexpr (std::is_void_v<context_type>) {
179 (void)ctx;
181 } else {
182 auto &typed = *static_cast<context_type *>(ctx);
183 sink_provider_traits<Tag>::write(value_ref<context_type>(typed), sv);
184 }
185 }
186
187 template <typename Tag> [[nodiscard]] static constexpr auto flush_entry() noexcept {
188 using context_type = typename sink_provider_traits<Tag>::context_type;
189 if constexpr (std::is_void_v<context_type>) {
190 if constexpr (detail::has_stateless_sink_provider_flush<Tag>::value) {
191 return +[](void *) noexcept { sink_provider_traits<Tag>::flush(); };
192 } else {
193 return static_cast<void (*)(void *) noexcept>(nullptr);
194 }
195 } else if constexpr (detail::has_sink_provider_flush<Tag>::value) {
196 return +[](void *ctx) noexcept {
197 auto &typed = *static_cast<context_type *>(ctx);
198 sink_provider_traits<Tag>::flush(value_ref<context_type>(typed));
199 };
200 } else {
201 return static_cast<void (*)(void *) noexcept>(nullptr);
202 }
203 }
204
205 template <typename Tag> static constexpr vtable s_vtbl{&write_entry<Tag>, flush_entry<Tag>()};
206
207 value_ptr<void> ctx_{};
208 const vtable *vtbl_{nullptr};
209};
210
219template <typename Tag> class sink_provider {
220public:
222 using context_type = typename traits_type::context_type;
223 static_assert(!std::is_void_v<context_type>, "sink_provider<Tag> requires a stateful backend; construct "
224 "sink_provider_ref{Tag{}} directly for stateless backends");
225
229 constexpr explicit sink_provider(context_type context) noexcept : context_(std::move(context)) {}
230
238 [[nodiscard]] constexpr sink_provider_ref ref() & noexcept RELOCO_LIFETIMEBOUND {
239 return sink_provider_ref(Tag{}, context_);
240 }
241
245 [[nodiscard]] constexpr sink as_sink() & noexcept RELOCO_LIFETIMEBOUND {
246 // Clang can't detect that ref() just forwards our context reference to context_
247#if defined(__clang__)
248#pragma clang diagnostic push
249#pragma clang diagnostic ignored "-Wreturn-stack-address"
250#pragma clang diagnostic ignored "-Wdangling-gsl"
251#endif
252
253 return ref().as_sink();
254
255#if defined(__clang__)
256#pragma clang diagnostic pop
257#endif
258 }
259
260private:
261 context_type context_;
262};
263
264// ============================================================================
265// Built-in Backends
266// ============================================================================
267
275
277 using context_type = void;
278
279 static void write(microfmt::string_view) noexcept {
280 // Intentionally discards every write.
281 }
282};
283
294
296 span<char> buffer;
297 size_t pos{0};
298};
299
302
303 static void write(value_ref<context_type> ctx, microfmt::string_view sv) noexcept {
304 const size_t avail = (ctx->pos < ctx->buffer.size()) ? (ctx->buffer.size() - ctx->pos) : 0;
305 const size_t n = std::min(sv.size(), avail);
306 if (n > 0) {
307 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
308 std::copy_n(sv.data(), n, ctx->buffer.data() + ctx->pos);
309 RELOCO_END_UNSAFE_BUFFER_USAGE;
310 ctx->pos += n;
311 }
312 }
313};
314
315} // namespace microfmt
Type-erased, two-word handle to a sink backend.
Definition sink_provider.hpp:78
constexpr sink_provider_ref(Tag, Context &&)=delete
constexpr bool can_flush() const noexcept
Reports whether the bound backend supports flush.
Definition sink_provider.hpp:148
constexpr sink as_sink() const noexcept RELOCO_LIFETIMEBOUND
Bridges to a plain microfmt::sink, for use with format_to/vformat_to.
Definition sink_provider.hpp:171
constexpr sink_provider_ref() noexcept=default
Constructs an empty (invalid) handle.
void flush() const noexcept
Flushes buffered output. A no-op when can_flush is false.
Definition sink_provider.hpp:153
constexpr sink_provider_ref(Tag, Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Constructs a handle for a stateful sink tag.
Definition sink_provider.hpp:125
void write(microfmt::string_view sv) const noexcept
Emits a sequence of characters to the underlying sink.
Definition sink_provider.hpp:139
Owning wrapper: holds context_type by value, sharing lifetime with the handle obtained from ref.
Definition sink_provider.hpp:219
constexpr sink_provider(context_type context) noexcept
Constructs the owning wrapper from an already-built context.
Definition sink_provider.hpp:229
typename traits_type::context_type context_type
Definition sink_provider.hpp:222
constexpr sink_provider_ref ref() &noexcept RELOCO_LIFETIMEBOUND
Borrows a sink_provider_ref bound to this wrapper's context.
Definition sink_provider.hpp:238
constexpr sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Convenience shorthand for ref().as_sink().
Definition sink_provider.hpp:245
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Core formatting primitives, sinks, and customization point.
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
Definition sink_provider.hpp:54
Tag for a stateless sink that discards every write.
Definition sink_provider.hpp:274
Virtual table of sink operations.
Definition sink_provider.hpp:83
static void write(microfmt::string_view) noexcept
Definition sink_provider.hpp:279
void context_type
Definition sink_provider.hpp:277
static void write(value_ref< context_type > ctx, microfmt::string_view sv) noexcept
Definition sink_provider.hpp:303
Static customization point describing a sink backend.
Definition sink_provider.hpp:50
Definition sink_provider.hpp:295
span< char > buffer
Definition sink_provider.hpp:296
Tag for a stateful sink that appends into a fixed-size microfmt::span of character storage,...
Definition sink_provider.hpp:293