Toolbox snapshot
The Reactive C++ Toolbox
Loading...
Searching...
No Matches
OStreamBase.hpp
Go to the documentation of this file.
1// The Reactive C++ Toolbox.
2// Copyright (C) 2025 Reactive Markets Limited
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#ifndef TOOLBOX_UTIL_OSTREAM_BASE_HPP
17#define TOOLBOX_UTIL_OSTREAM_BASE_HPP
18
19#include <algorithm>
20#include <cassert>
21#include <charconv>
22#include <concepts>
23#include <cstring>
24#include <limits>
25#include <string>
26#include <string_view>
27#include <system_error>
28#include <type_traits>
29
30#include "TypeTraits.hpp"
31
32namespace toolbox {
33inline namespace util {
34
35/*
36DerivedT is required to implement the following:
37
381) char* do_prepare_space(std::size_t num_bytes)
39 - returns writable buffer of atleast `num_bytes` bytes for immediate writing
40 - will be followed by a call to `do_relinquish_space` consuming up to `num_bytes`
41
422) void do_relinquish_space(std::size_t consumed_num_bytes)
43 - data of size `consumed_num_bytes` was written into given buffer (by `do_prepare_space`)
44 - The remaining unconsumed bytes are relinquished back to derived class
45
463) void do_set_badbit()
47 - informs derived type of failure when outputting, specifically invoked on:
48 - failure to allocate space
49 - failure to output
50*/
51
52template <class DerivedT>
53class OStreamBase;
54
55namespace detail {
56template <class T>
58
59template <class T>
60concept AllowedIntegral = std::is_integral_v<T> && !ExcludedIntegral<T>;
61
62template <class T>
63concept AllowedNumeric = AllowedIntegral<T> || std::floating_point<T>;
64
65template <class T>
67
68template <class T>
70 = std::is_base_of_v<OStreamBase<std::remove_cvref_t<T>>, std::remove_cvref_t<T>>;
71
72} // namespace detail
73
74template <class DerivedT>
76 public:
77 DerivedT& put_data(const char* data, std::size_t data_size);
78
79 template <class T>
82
84
85 template <class T>
86 requires(detail::AllowedIntegral<T> && !std::same_as<T, bool>)
88
89 template <class T>
90 requires std::floating_point<T>
92
94 DerivedT& put(char ch);
95 DerivedT& write(const char* data, std::size_t sz);
96
97 protected:
98 // A number requires exactly N bytes to print, however put_num() will request a larger buffer
99 // size for performance reasons (ofcourse, when the buffer is relinquished it will correctly
100 // indicated that N bytes are consumed)
101 // However, if derived class cannot fullfill this request, then will fallback to slower path
102 // that requests exactly the N bytes required to print the number.
103 static constexpr std::size_t PutNumMaxBufRequest = 32u;
104
105 private:
106 DerivedT& get_derived() noexcept;
107
108 char* prepare_space(std::size_t num_bytes);
109 void relinquish_space(std::size_t consumed_num_bytes);
110 void set_badbit();
111};
112
114DerivedT& OStreamBase<DerivedT>::get_derived() noexcept
115{
116 // sizeof(T) results in a compiler error if T is an incomplete type
117 static_assert(sizeof(DerivedT) >= 0, "incomplete derived type");
118 static_assert(std::is_base_of_v<OStreamBase<DerivedT>, DerivedT>);
119 return static_cast<DerivedT&>(*this);
120}
121
122template <class DerivedT>
124{
125 return get_derived().do_prepare_space(num_bytes);
126}
127
128template <class DerivedT>
130{
131 return get_derived().do_relinquish_space(consumed_num_bytes);
132}
133
134template <class DerivedT>
135void OStreamBase<DerivedT>::set_badbit()
136{
137 get_derived().do_set_badbit();
138}
139
140template <class DerivedT>
142{
143 char* buf = prepare_space(data_size);
144 if (buf != nullptr) [[likely]] {
145 std::memcpy(buf, data, data_size);
146 relinquish_space(data_size);
147 } else {
148 set_badbit();
149 }
150 return get_derived();
151}
152
153template <class DerivedT>
154template <class T>
157{
158 char* buf = prepare_space(1);
159 if (buf != nullptr) [[likely]] {
160 *buf = ch;
161 relinquish_space(1);
162 } else {
163 set_badbit();
164 }
165 return get_derived();
166}
167
168template <class DerivedT>
170{
171 put_char(val ? '1' : '0');
172 return get_derived();
173}
174
175template <class DerivedT>
176template <class T>
177 requires(detail::AllowedIntegral<T> && !std::same_as<T, bool>)
179{
180 constexpr std::size_t MaxBytesNeeded
181 = dec_digits(std::numeric_limits<T>::max()) + std::is_signed_v<T>;
182
183 static_assert(MaxBytesNeeded <= PutNumMaxBufRequest);
184
185 char* buf = prepare_space(MaxBytesNeeded);
186 std::size_t buf_sz = MaxBytesNeeded;
187
188 if (buf == nullptr) [[unlikely]] {
189 // slightly slower path
190 auto digits = dec_digits(val);
191 if constexpr (std::is_signed_v<T>) {
192 digits += (val < 0);
193 }
194 buf = prepare_space(digits);
195 buf_sz = digits;
196 }
197
198 if (buf != nullptr) [[likely]] {
199 // impossible for it to fail (N.B. to_chars is non-throwing)
200 const auto [end, ec] = std::to_chars(buf, buf + buf_sz, val);
201 assert(ec == std::errc());
202 relinquish_space(end - buf);
203 } else {
204 set_badbit();
205 }
206
207 return get_derived();
208}
209
210template <class DerivedT>
211template <class T>
212 requires std::floating_point<T>
214{
215 char* buf = prepare_space(PutNumMaxBufRequest);
216 if (buf != nullptr) [[likely]] { // fast path
217 // impossible for to_chars to fail (N.B. to_chars is non-throwing)
218 const auto [end, ec] = std::to_chars(buf, buf + PutNumMaxBufRequest, val);
219 relinquish_space(end - buf);
220 assert(ec == std::errc());
221 } else {
222 // slower path
223 // impossible for to_chars to fail (N.B. to_chars is non-throwing)
225 const auto [end, ec] = std::to_chars(local_space, local_space + PutNumMaxBufRequest, val);
226 assert(ec == std::errc());
227
228 std::size_t consumed = end - local_space;
229 buf = prepare_space(consumed);
230
231 if (buf != nullptr) [[likely]] {
232 std::copy(local_space, end, buf);
233 relinquish_space(consumed);
234 } else {
235 set_badbit();
236 }
237 }
238
239 return get_derived();
240}
241
242template <class DerivedT>
244{
245 return put_char(ch);
246}
247
248template <class DerivedT>
250{
251 return put_data(data, sz);
252}
253
254template <class StreamT>
256StreamT& operator<<(StreamT&& os, bool value)
257{
258 os.put_num(value);
259 return os;
260}
261
262template <class StreamT>
263 requires detail::InheritsBasicOStream<StreamT>
264StreamT& operator<<(StreamT&& os, short value)
265{
266 os.put_num(value);
267 return os;
268}
269
270template <class StreamT>
271 requires detail::InheritsBasicOStream<StreamT>
272StreamT& operator<<(StreamT&& os, unsigned short value)
273{
274 os.put_num(value);
275 return os;
276}
277
278template <class StreamT>
279 requires detail::InheritsBasicOStream<StreamT>
280StreamT& operator<<(StreamT&& os, int value)
281{
282 os.put_num(value);
283 return os;
284}
285
286template <class StreamT>
287 requires detail::InheritsBasicOStream<StreamT>
288StreamT& operator<<(StreamT&& os, unsigned int value)
289{
290 os.put_num(value);
291 return os;
292}
293
294template <class StreamT>
295 requires detail::InheritsBasicOStream<StreamT>
296StreamT& operator<<(StreamT&& os, long value)
297{
298 os.put_num(value);
299 return os;
300}
301
302template <class StreamT>
303 requires detail::InheritsBasicOStream<StreamT>
304StreamT& operator<<(StreamT&& os, unsigned long value)
305{
306 os.put_num(value);
307 return os;
308}
309
310template <class StreamT>
311 requires detail::InheritsBasicOStream<StreamT>
312StreamT& operator<<(StreamT&& os, long long value)
313{
314 os.put_num(value);
315 return os;
316}
317
318template <class StreamT>
319 requires detail::InheritsBasicOStream<StreamT>
320StreamT& operator<<(StreamT&& os, unsigned long long value)
321{
322 os.put_num(value);
323 return os;
324}
325
326template <class StreamT>
327 requires detail::InheritsBasicOStream<StreamT>
328StreamT& operator<<(StreamT&& os, float value)
329{
330 os.put_num(value);
331 return os;
332}
333
334template <class StreamT>
335 requires detail::InheritsBasicOStream<StreamT>
336StreamT& operator<<(StreamT&& os, double value)
337{
338 os.put_num(value);
339 return os;
340}
341
342template <class StreamT>
343 requires detail::InheritsBasicOStream<StreamT>
344StreamT& operator<<(StreamT&& os, long double value)
345{
346 os.put_num(value);
347 return os;
348}
349
350template <class StreamT>
351 requires detail::InheritsBasicOStream<StreamT>
353{
354 os.put_char(ch);
355 return os;
356}
357
358template <class StreamT>
359 requires detail::InheritsBasicOStream<StreamT>
360StreamT& operator<<(StreamT& os, signed char ch)
361{
362 os.put_char(ch);
363 return os;
364}
365
366template <class StreamT>
367 requires detail::InheritsBasicOStream<StreamT>
368StreamT& operator<<(StreamT& os, unsigned char ch)
369{
370 os.put_char(ch);
371 return os;
372}
373
374template <class StreamT>
375 requires detail::InheritsBasicOStream<StreamT>
376StreamT& operator<<(StreamT& os, const char* s)
377{
378 std::size_t len = std::char_traits<char>::length(s);
379 os.put_data(s, len);
380 return os;
381}
382
383template <class StreamT>
384 requires detail::InheritsBasicOStream<StreamT>
385StreamT& operator<<(StreamT& os, const signed char* s)
386{
387 const char* data = std::bit_cast<const char*>(s);
388 std::size_t len = std::char_traits<char>::length(data);
389 os.put_data(data, len);
390 return os;
391}
392
393template <class StreamT>
394 requires detail::InheritsBasicOStream<StreamT>
395StreamT& operator<<(StreamT& os, const unsigned char* s)
396{
397 const char* data = std::bit_cast<const char*>(s);
398 std::size_t len = std::char_traits<char>::length(data);
399 os.put_data(data, len);
400 return os;
401}
402
403template <class StreamT>
404 requires detail::InheritsBasicOStream<StreamT>
405StreamT& operator<<(StreamT& os, const std::string& s)
406{
407 os.put_data(s.data(), s.size());
408 return os;
409}
410
411template <class StreamT>
412 requires detail::InheritsBasicOStream<StreamT>
413StreamT& operator<<(StreamT& os, std::string_view s)
414{
415 os.put_data(s.data(), s.size());
416 return os;
417}
418
419template <class StreamT>
420 requires detail::InheritsBasicOStream<StreamT>
421StreamT& operator<<(StreamT& os, const std::error_code& ec)
422{
423 const char* category = ec.category().name();
424 std::size_t category_len = std::char_traits<char>::length(category);
425
426 os.put_data(category, category_len);
427 os.put_char(':');
428 os.put_num(ec.value());
429
430 return os;
431}
432
433template <class StreamT, class T>
434 requires(detail::InheritsBasicOStream<StreamT> && std::is_rvalue_reference_v<StreamT &&>)
435StreamT&& operator<<(StreamT&& os, const T& value)
436{
437 os << value;
438 return static_cast<StreamT&&>(os);
439}
440
441} // namespace util
442} // namespace toolbox
443
444#endif // TOOLBOX_UTIL_OSTREAM_BASE_HPP
DerivedT & put_num(bool val)
static constexpr std::size_t PutNumMaxBufRequest
DerivedT & write(const char *data, std::size_t sz)
DerivedT & put_num(T val)
DerivedT & put_num(T val)
DerivedT & put(char ch)
For compatability with std::ostream API.
DerivedT & put_data(const char *data, std::size_t data_size)
STL namespace.
ostream & operator<<(ostream &os, const pair< T, U > &p)
Definition Parser.ut.cpp:29
const DataT & data(const MsgEvent &ev) noexcept
Definition Event.hpp:56
constexpr int dec_digits(ValueT i) noexcept
Definition Utility.hpp:52
constexpr auto bind() noexcept
Definition Slot.hpp:97