microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
container_sink.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
7// std-interop-file: opt-in sink/formatting adapters defaulting to std::string/std::vector containers.
8
11#include "../microfmt.hpp"
12#include <cstddef>
13#include <new>
14#include <reloco/allocator.hpp>
15#include <reloco/error.hpp>
16#include <string>
17#include <string_view>
18#include <type_traits>
19#include <utility>
20#include <vector>
21
22namespace microfmt {
23
24namespace detail {
25
26template <typename Container, typename = void> struct has_data_size_impl : std::false_type {};
27
28template <typename Container>
30 Container, std::void_t<decltype(std::declval<Container &>().data()), decltype(std::declval<Container &>().size())>>
31 : std::integral_constant<
32 bool, std::is_convertible<decltype(std::declval<Container &>().data()), const char *>::value &&
33 std::is_convertible<decltype(std::declval<Container &>().size()), std::size_t>::value> {};
34
35template <typename Container, typename = void> struct has_std_push_back : std::false_type {};
36
37template <typename Container>
38struct has_std_push_back<Container, std::void_t<decltype(std::declval<Container &>().push_back(char{}))>>
39 : std::true_type {};
40
41// Fallible (reloco-style) containers report allocation failure through a result instead of throwing.
42template <typename Container, typename = void> struct has_try_push_back : std::false_type {};
43
44template <typename Container>
45struct has_try_push_back<Container,
46 std::void_t<decltype(static_cast<bool>(std::declval<Container &>().try_push_back(char{})))>>
47 : std::true_type {};
48
49template <typename Container, typename = void> struct has_try_append : std::false_type {};
50
51template <typename Container>
52struct has_try_append<Container, std::void_t<decltype(static_cast<bool>(std::declval<Container &>().try_append(
53 microfmt::string_view{})))>> : std::true_type {};
54
55template <typename Container>
57 : std::integral_constant<bool, has_data_size_impl<Container>::value &&
58 (has_std_push_back<Container>::value || has_try_push_back<Container>::value)> {};
59
60template <typename Container, typename = void> struct has_append : std::false_type {};
61
62template <typename Container>
63struct has_append<Container, std::void_t<decltype(std::declval<Container &>().append(
64 std::declval<const char *>(), std::declval<std::size_t>()))>> : std::true_type {};
65
66template <typename Container, typename = void> struct has_range_insert : std::false_type {};
67
68template <typename Container>
70 Container, std::void_t<decltype(std::declval<Container &>().insert(
71 std::declval<Container &>().end(), std::declval<const char *>(), std::declval<const char *>()))>>
72 : std::true_type {};
73
74} // namespace detail
75
76template <typename Container>
78
79// ============================================================================
80// Dynamic Container Sink Adapter
81// ============================================================================
82
83template <typename Container, typename std::enable_if<is_growable_char_container<Container>, int>::type = 0>
84class RELOCO_POINTER container_sink {
85public:
86 explicit constexpr container_sink(Container &target RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
87 : target_(&target) {}
88
89 container_sink(const container_sink &) = delete;
91 container_sink(container_sink &&) noexcept = default;
92 container_sink &operator=(container_sink &&) noexcept = default;
93
94 [[nodiscard]] sink as_sink() noexcept RELOCO_LIFETIMEBOUND {
95 return sink{this,
96 [](void *ctx, microfmt::string_view sv) noexcept { static_cast<container_sink *>(ctx)->write(sv); }};
97 }
98
99 void put(char c) noexcept { write(microfmt::string_view(&c, 1)); }
100
103 [[nodiscard]] bool failed() const noexcept { return failed_; }
104
105 // Never throws: the sink callback is noexcept, so std container exceptions are caught and recorded.
106 // bridges to a C/platform API (FILE*, syslog, android log, raw chunk storage) that takes raw pointers.
107 RELOCO_BEGIN_UNSAFE_CODE
108 void write(microfmt::string_view sv) noexcept {
109 if (failed_) {
110 return;
111 }
113 write_fallible(sv);
114 } else {
115#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
116 try {
117 write_std(sv);
118 } catch (...) { // std-interop-ok: std containers report allocation failure by throwing
119 failed_ = true;
120 }
121#else
122 write_std(sv);
123#endif
124 }
125 }
127
128private:
129 void write_fallible(microfmt::string_view sv) noexcept {
131 if (!target_->try_append(sv)) {
132 failed_ = true;
133 }
134 } else {
135 for (char c : sv) {
136 if (!target_->try_push_back(c)) {
137 failed_ = true;
138 return;
139 }
140 }
141 }
142 }
143
144 // NOLINTNEXTLINE(reloco-noexcept): std container growth may throw; the caller (write) catches and records it
145 void write_std(microfmt::string_view sv) {
146 if constexpr (detail::has_append<Container>::value) {
147 target_->append(sv.data(), sv.size());
148 } else if constexpr (detail::has_range_insert<Container>::value) {
149 target_->insert(target_->end(), sv.begin(), sv.end());
150 } else {
151 for (char c : sv) {
152 target_->push_back(c);
153 }
154 }
155 }
156
157 value_ptr<Container> target_;
158 bool failed_ = false;
159};
160
161// Helper factory for deduction
162template <typename Container, typename std::enable_if<is_growable_char_container<Container>, int>::type = 0>
163[[nodiscard]] constexpr auto make_container_sink(Container &c RELOCO_LIFETIMEBOUND) noexcept {
165}
166
167// ============================================================================
168// Direct Append & Allocation Helpers
169// ============================================================================
170
171// Appends formatted text to an existing growable character container.
172// bridges to a C/platform API (FILE*, syslog, android log, raw chunk storage) that takes raw pointers.
173RELOCO_BEGIN_UNSAFE_CODE
174template <typename Container, typename... Args>
175typename std::enable_if<is_growable_char_container<Container>, void>::type
176// NOLINTNEXTLINE(reloco-noexcept): throws std::bad_alloc on growth failure to preserve the std container contract
177format_to_container(Container &dest, microfmt::string_view fmt_str, const Args &...args) {
179 auto out = cs.as_sink();
180 format_to(out, fmt_str, args...);
181#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
182 if (cs.failed()) {
183 throw std::bad_alloc(); // std-interop-ok: preserve the container's throwing contract
184 }
185#endif
186}
188
189// Returns a newly allocated container.
190template <typename Container = std::string, typename... Args>
191[[nodiscard]] typename std::enable_if<is_growable_char_container<Container>, Container>::type
192// NOLINTNEXTLINE(reloco-noexcept): throws std::bad_alloc on growth failure to preserve the std container contract
193format_as_container(microfmt::string_view fmt_str, const Args &...args) {
194 Container result;
195 format_to_container(result, fmt_str, args...);
196 return result;
197}
198
199// Formats into a new container bound to `alloc`. Fails with reloco::error::allocation_failed if the
200// container could not grow to hold the whole output. Requires an allocator_ref-constructible container
201// (e.g. reloco::string, reloco::vector<char>).
202template <typename Container, typename... Args>
203[[nodiscard]] typename std::enable_if<is_growable_char_container<Container> &&
204 std::is_constructible<Container, reloco::allocator_ref>::value,
205 reloco::result<Container>>::type
206// NOLINTNEXTLINE(reloco-noexcept): throws std::bad_alloc on growth failure to preserve the std container contract
207try_format_as_container(reloco::allocator_ref alloc, microfmt::string_view fmt_str, const Args &...args) {
208 Container result(alloc);
209 container_sink<Container> cs(result);
210 format_to(cs.as_sink(), fmt_str, args...);
211 if (cs.failed()) {
212 return reloco::unexpected(reloco::error::allocation_failed);
213 }
214 return result;
215}
216
217// Appends to an existing container; the container's own allocator is used.
218template <typename Container, typename... Args>
219[[nodiscard]] typename std::enable_if<is_growable_char_container<Container>, reloco::result<void>>::type
220// NOLINTNEXTLINE(reloco-noexcept): throws std::bad_alloc on growth failure to preserve the std container contract
221try_format_to_container(Container &dest, microfmt::string_view fmt_str, const Args &...args) {
223 format_to(cs.as_sink(), fmt_str, args...);
224 if (cs.failed()) {
225 return reloco::unexpected(reloco::error::allocation_failed);
226 }
227 return {};
228}
229
230} // namespace microfmt
Definition container_sink.hpp:84
constexpr container_sink(Container &target RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Definition container_sink.hpp:86
void put(char c) noexcept
Definition container_sink.hpp:99
container_sink & operator=(const container_sink &)=delete
bool failed() const noexcept
Definition container_sink.hpp:103
RELOCO_BEGIN_UNSAFE_CODE void write(microfmt::string_view sv) noexcept
Definition container_sink.hpp:108
container_sink(container_sink &&) noexcept=default
container_sink(const container_sink &)=delete
Definition assert.hpp:19
std::enable_if< is_growable_char_container< Container > &&std::is_constructible< Container, reloco::allocator_ref >::value, reloco::result< Container > >::type try_format_as_container(reloco::allocator_ref alloc, microfmt::string_view fmt_str, const Args &...args)
Definition container_sink.hpp:207
constexpr auto make_container_sink(Container &c RELOCO_LIFETIMEBOUND) noexcept
Definition container_sink.hpp:163
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
std::enable_if< is_growable_char_container< Container >, reloco::result< void > >::type try_format_to_container(Container &dest, microfmt::string_view fmt_str, const Args &...args)
Definition container_sink.hpp:221
constexpr bool is_growable_char_container
Definition container_sink.hpp:77
RELOCO_END_UNSAFE_CODE std::enable_if< is_growable_char_container< Container >, Container >::type format_as_container(microfmt::string_view fmt_str, const Args &...args)
Definition container_sink.hpp:193
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
RELOCO_BEGIN_UNSAFE_CODE std::enable_if< is_growable_char_container< Container >, void >::type format_to_container(Container &dest, microfmt::string_view fmt_str, const Args &...args)
Definition container_sink.hpp:177
Definition container_sink.hpp:60
Definition container_sink.hpp:26
Definition container_sink.hpp:66
Definition container_sink.hpp:35
Definition container_sink.hpp:49
Definition container_sink.hpp:42