microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
memory_buffer.hpp
Go to the documentation of this file.
1#pragma once
3#include <algorithm>
4#include <reloco/default_allocator.hpp>
5
6namespace microfmt {
7
8RELOCO_BEGIN_UNSAFE_BUFFER_USAGE
9
10namespace detail {
11
20public:
21 using value_type = char;
22 using size_type = std::size_t;
23 using difference_type = std::ptrdiff_t;
25 using const_reference = const value_type &;
27 using const_pointer = const value_type *;
30
33
35
37 if (m_data != m_inline_ptr) {
38 m_alloc.deallocate(m_data, m_capacity);
39 }
40 }
41
45 [[nodiscard]] sink as_sink() & noexcept RELOCO_LIFETIMEBOUND {
46 return sink{
47 this, [](void *ctx, microfmt::string_view sv) noexcept { static_cast<memory_buffer_base *>(ctx)->append(sv); }};
48 }
49
50 [[nodiscard]] constexpr microfmt::string_view view() const & noexcept RELOCO_LIFETIMEBOUND {
51 return microfmt::string_view(m_data, m_size);
52 }
53
54 // --- STL Range & Capacity Observers ---
55 [[nodiscard]] constexpr pointer data() & noexcept RELOCO_LIFETIMEBOUND { return m_data; }
56 [[nodiscard]] constexpr const_pointer data() const & noexcept RELOCO_LIFETIMEBOUND { return m_data; }
57
58 [[nodiscard]] constexpr iterator begin() & noexcept RELOCO_LIFETIMEBOUND { return m_data; }
59 [[nodiscard]] constexpr const_iterator begin() const & noexcept RELOCO_LIFETIMEBOUND { return m_data; }
60 [[nodiscard]] constexpr const_iterator cbegin() const & noexcept RELOCO_LIFETIMEBOUND { return m_data; }
61
62 [[nodiscard]] constexpr iterator end() & noexcept RELOCO_LIFETIMEBOUND { return m_data + m_size; }
63 [[nodiscard]] constexpr const_iterator end() const & noexcept RELOCO_LIFETIMEBOUND { return m_data + m_size; }
64 [[nodiscard]] constexpr const_iterator cend() const & noexcept RELOCO_LIFETIMEBOUND { return m_data + m_size; }
65
66 [[nodiscard]] constexpr size_type size() const noexcept { return m_size; }
67 [[nodiscard]] constexpr size_type capacity() const noexcept { return m_capacity; }
68 [[nodiscard]] constexpr bool empty() const noexcept { return m_size == 0; }
69
70 [[nodiscard]] constexpr reference operator[](size_type pos) & noexcept RELOCO_LIFETIMEBOUND {
71 RELOCO_ASSERT(pos < size(), "Index out of bounds");
72 return m_data[pos];
73 }
74 [[nodiscard]] constexpr const_reference operator[](size_type pos) const & noexcept RELOCO_LIFETIMEBOUND {
75 RELOCO_ASSERT(pos < size(), "Index out of bounds");
76 return m_data[pos];
77 }
78
79 [[nodiscard]] constexpr reference front() & noexcept RELOCO_LIFETIMEBOUND {
80 RELOCO_ASSERT(!empty(), "Container is empty");
81 return m_data[0];
82 }
83 [[nodiscard]] constexpr const_reference front() const & noexcept RELOCO_LIFETIMEBOUND {
84 RELOCO_ASSERT(!empty(), "Container is empty");
85 return m_data[0];
86 }
87 [[nodiscard]] constexpr reference back() & noexcept RELOCO_LIFETIMEBOUND {
88 RELOCO_ASSERT(!empty(), "Container is empty");
89 return m_data[m_size - 1];
90 }
91 [[nodiscard]] constexpr const_reference back() const & noexcept RELOCO_LIFETIMEBOUND {
92 RELOCO_ASSERT(!empty(), "Container is empty");
93 return m_data[m_size - 1];
94 }
95
96 RELOCO_REINITIALIZES void clear() & noexcept { m_size = 0; }
97
101 void push_back(char c) & noexcept {
102 if (m_size >= m_capacity) {
103 if (!grow(m_size + 1))
104 return; // Truncate safely on OOM in -fno-exceptions
105 }
106 m_data[m_size++] = c;
107 }
108
109protected:
113 constexpr memory_buffer_base(reloco::allocator_ref alloc, char *inline_buf, std::size_t inline_cap) noexcept
114 : m_alloc(alloc), m_data(inline_buf), m_inline_ptr(inline_buf), m_size(0), m_capacity(inline_cap) {}
115
119 constexpr memory_buffer_base(memory_buffer_base &&other, char *new_inline_ptr, std::size_t inline_cap) noexcept
120 : m_alloc(other.m_alloc), m_data(other.m_data == other.m_inline_ptr ? new_inline_ptr : other.m_data),
121 m_inline_ptr(new_inline_ptr), m_size(other.m_size), m_capacity(other.m_capacity) {
122
123 // If the source was using its inline stack buffer, copy the bytes
124 if (other.m_data == other.m_inline_ptr) {
125 std::copy_n(other.m_inline_ptr, other.m_size, new_inline_ptr);
126 }
127
128 // Reset source to a safe, empty state pointing to its own stack buffer
129 other.m_data = other.m_inline_ptr;
130 other.m_size = 0;
131 other.m_capacity = inline_cap;
132 }
133
137 void move_assign(memory_buffer_base &&other, std::size_t inline_cap) noexcept {
138 if (this == &other)
139 return;
140
141 // Free our existing heap memory if we had any
142 if (m_data != m_inline_ptr) {
143 m_alloc.deallocate(m_data, m_capacity);
144 }
145
146 m_alloc = other.m_alloc;
147 m_size = other.m_size;
148 m_capacity = other.m_capacity;
149
150 if (other.m_data == other.m_inline_ptr) {
151 // Source is on stack: copy the bytes into OUR inline buffer
152 m_data = m_inline_ptr;
153 std::copy_n(other.m_inline_ptr, other.m_size, m_inline_ptr);
154 } else {
155 // Source is on heap: steal the pointer directly
156 m_data = other.m_data;
157 }
158
159 // Reset source to a safe, empty state
160 other.m_data = other.m_inline_ptr;
161 other.m_size = 0;
162 other.m_capacity = inline_cap;
163 }
164
165private:
166 reloco::allocator_ref m_alloc;
167 char *m_data;
168 char *m_inline_ptr; // Keeps track of the stack buffer address to know when to deallocate
169 std::size_t m_size;
170 std::size_t m_capacity;
171
172 void append(microfmt::string_view sv) noexcept {
173 if (sv.empty())
174 return;
175
176 if (m_size + sv.size() > m_capacity) {
177 if (!grow(m_size + sv.size())) {
178 // Out of memory: truncate safely.
179 const std::size_t avail = m_capacity - m_size;
180 if (avail > 0) {
181 std::copy_n(sv.data(), avail, m_data + m_size);
182 m_size += avail;
183 }
184 return;
185 }
186 }
187
188 std::copy_n(sv.data(), sv.size(), m_data + m_size);
189 m_size += sv.size();
190 }
191
192 [[nodiscard]] bool grow(std::size_t required_capacity) noexcept {
193 std::size_t new_capacity = m_capacity + (m_capacity / 2);
194 if (new_capacity < required_capacity) {
195 new_capacity = required_capacity;
196 }
197
198 if (m_data == m_inline_ptr) {
199 // Transition from Stack to Heap
200 const auto new_mem = m_alloc.allocate(new_capacity, alignof(char));
201 if (!new_mem)
202 return false;
203
204 char *new_data = static_cast<char *>(new_mem->ptr);
205 std::copy_n(m_inline_ptr, m_size, new_data);
206
207 m_data = new_data;
208 m_capacity = new_capacity;
209 } else {
210 // Already on Heap: Try expand_in_place first
211 if (const auto actual_capacity = m_alloc.expand_in_place(m_data, m_capacity, new_capacity);
212 actual_capacity && actual_capacity.value() >= new_capacity) {
213 m_capacity = actual_capacity.value();
214 } else {
215 // Fallback to standard reallocate
216 const auto new_mem = m_alloc.reallocate(m_data, m_capacity, new_capacity, alignof(char));
217 if (!new_mem)
218 return false;
219
220 m_data = static_cast<char *>(new_mem->ptr);
221 m_capacity = new_capacity;
222 }
223 }
224 return true;
225 }
226};
227
228} // namespace detail
229
230RELOCO_END_UNSAFE_BUFFER_USAGE
231
240template <std::size_t InlineCapacity = 512> class memory_buffer : public detail::memory_buffer_base {
241public:
242 explicit constexpr memory_buffer(reloco::allocator_ref alloc) noexcept
243 : detail::memory_buffer_base(alloc, m_inline, InlineCapacity) {}
244
245 constexpr memory_buffer() noexcept : memory_buffer(reloco::default_allocator()) {}
246
247 // Move constructor delegates to base, providing the new inline stack boundary
248 constexpr memory_buffer(memory_buffer &&other) noexcept
249 : detail::memory_buffer_base(std::move(other), m_inline, InlineCapacity) {}
250
251 // Move assignment delegates to base's controlled move_assign method
253 this->move_assign(std::move(other), InlineCapacity);
254 return *this;
255 }
256
257private:
258 alignas(void *) char m_inline[InlineCapacity];
259};
260
261} // namespace microfmt
262
263template <> struct reloco::is_trivially_relocatable<microfmt::detail::memory_buffer_base> : std::false_type {};
264template <std::size_t N> struct reloco::is_trivially_relocatable<microfmt::memory_buffer<N>> : std::false_type {};
Non-templated base class for dynamically growing buffers.
Definition memory_buffer.hpp:19
constexpr reference back() &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:87
memory_buffer_base & operator=(const memory_buffer_base &)=delete
constexpr reference front() &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:79
void push_back(char c) &noexcept
Fast-path single character append (enables std::back_inserter compatibility).
Definition memory_buffer.hpp:101
const_pointer const_iterator
Definition memory_buffer.hpp:29
constexpr const_iterator end() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:63
~memory_buffer_base() noexcept
Definition memory_buffer.hpp:36
constexpr pointer data() &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:55
memory_buffer_base(const memory_buffer_base &)=delete
constexpr const_iterator cend() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:64
constexpr memory_buffer_base(reloco::allocator_ref alloc, char *inline_buf, std::size_t inline_cap) noexcept
Initializes the buffer state using the derived class's stack storage.
Definition memory_buffer.hpp:113
constexpr const_reference front() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:83
std::ptrdiff_t difference_type
Definition memory_buffer.hpp:23
std::size_t size_type
Definition memory_buffer.hpp:22
RELOCO_REINITIALIZES void clear() &noexcept
Definition memory_buffer.hpp:96
RELOCO_BLOCK_RVALUE_ACCESS(memory_buffer_base)
constexpr const_pointer data() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:56
constexpr reference operator[](size_type pos) &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:70
constexpr const_iterator cbegin() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:60
sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to this instance.
Definition memory_buffer.hpp:45
constexpr iterator begin() &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:58
const value_type & const_reference
Definition memory_buffer.hpp:25
constexpr bool empty() const noexcept
Definition memory_buffer.hpp:68
const value_type * const_pointer
Definition memory_buffer.hpp:27
constexpr iterator end() &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:62
void move_assign(memory_buffer_base &&other, std::size_t inline_cap) noexcept
Safely move-assigns the buffer, handling heap/stack transitions.
Definition memory_buffer.hpp:137
constexpr const_reference back() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:91
constexpr memory_buffer_base(memory_buffer_base &&other, char *new_inline_ptr, std::size_t inline_cap) noexcept
Move-constructs from another buffer base, resolving inline pointers.
Definition memory_buffer.hpp:119
pointer iterator
Definition memory_buffer.hpp:28
char value_type
Definition memory_buffer.hpp:21
constexpr const_reference operator[](size_type pos) const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:74
constexpr size_type size() const noexcept
Definition memory_buffer.hpp:66
value_type & reference
Definition memory_buffer.hpp:24
constexpr size_type capacity() const noexcept
Definition memory_buffer.hpp:67
constexpr const_iterator begin() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:59
value_type * pointer
Definition memory_buffer.hpp:26
constexpr microfmt::string_view view() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_buffer.hpp:50
A dynamically growing buffer with inline stack storage.
Definition memory_buffer.hpp:240
constexpr memory_buffer(reloco::allocator_ref alloc) noexcept
Definition memory_buffer.hpp:242
constexpr memory_buffer() noexcept
Definition memory_buffer.hpp:245
constexpr memory_buffer(memory_buffer &&other) noexcept
Definition memory_buffer.hpp:248
memory_buffer & operator=(memory_buffer &&other) noexcept
Definition memory_buffer.hpp:252
#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 debug_float.hpp:36