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 <charconv>
21#include <concepts>
22#include <cstring>
23#include <system_error>
24#include <type_traits>
25#include <string>
26#include <string_view>
27#include <cassert>
28#include <limits>
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> requires detail::AllowedChar<T>
156{
157 char* buf = prepare_space(1);
158 if (buf != nullptr) [[likely]] {
159 *buf = ch;
160 relinquish_space(1);
161 } else {
162 set_badbit();
163 }
164 return get_derived();
165}
166
167template <class DerivedT>
169{
170 put_char(val ? '1' : '0');
171 return get_derived();
172}
173
174template <class DerivedT>
175template <class T> requires (detail::AllowedIntegral<T> && !std::same_as<T, bool>)
177{
178 constexpr std::size_t MaxBytesNeeded
179 = dec_digits(std::numeric_limits<T>::max())
180 + std::is_signed_v<T>;
181
182 static_assert(MaxBytesNeeded <= PutNumMaxBufRequest);
183
184 char* buf = prepare_space(MaxBytesNeeded);
185 std::size_t buf_sz = MaxBytesNeeded;
186
187 if (buf == nullptr) [[unlikely]] {
188 // slightly slower path
189 auto digits = dec_digits(val);
190 if constexpr (std::is_signed_v<T>) {
191 digits += (val < 0);
192 }
193 buf = prepare_space(digits);
194 buf_sz = digits;
195 }
196
197 if (buf != nullptr) [[likely]] {
198 // impossible for it to fail (N.B. to_chars is non-throwing)
199 const auto [end, ec] = std::to_chars(buf, buf + buf_sz, val);
200 assert(ec == std::errc());
201 relinquish_space(end - buf);
202 } else {
203 set_badbit();
204 }
205
206 return get_derived();
207}
208
209template <class DerivedT>
210template <class T> requires std::floating_point<T>
212{
213 char* buf = prepare_space(PutNumMaxBufRequest);
214 if (buf != nullptr) [[likely]] { // fast path
215 // impossible for to_chars to fail (N.B. to_chars is non-throwing)
216 const auto [end, ec] = std::to_chars(buf, buf + PutNumMaxBufRequest, val);
217 relinquish_space(end - buf);
218 assert(ec == std::errc());
219 }
220 else {
221 // slower path
222 // impossible for to_chars to fail (N.B. to_chars is non-throwing)
224 const auto [end, ec] = std::to_chars(local_space, local_space + PutNumMaxBufRequest, val);
225 assert(ec == std::errc());
226
227 std::size_t consumed = end - local_space;
228 buf = prepare_space(consumed);
229
230 if (buf != nullptr) [[likely]] {
231 std::copy(local_space, end, buf);
232 relinquish_space(consumed);
233 } else {
234 set_badbit();
235 }
236 }
237
238 return get_derived();
239}
240
241template <class DerivedT>
243{
244 return put_char(ch);
245}
246
247template <class DerivedT>
249{
250 return put_data(data, sz);
251}
252
253template <class StreamT>
255StreamT& operator<<(StreamT&& os, bool value)
256{
257 os.put_num(value);
258 return os;
259}
260
261template <class StreamT>
262 requires detail::InheritsBasicOStream<StreamT>
263StreamT& operator<<(StreamT&& os, short value)
264{
265 os.put_num(value);
266 return os;
267}
268
269template <class StreamT>
270 requires detail::InheritsBasicOStream<StreamT>
271StreamT& operator<<(StreamT&& os, unsigned short value)
272{
273 os.put_num(value);
274 return os;
275}
276
277template <class StreamT>
278 requires detail::InheritsBasicOStream<StreamT>
279StreamT& operator<<(StreamT&& os, int value)
280{
281 os.put_num(value);
282 return os;
283}
284
285template <class StreamT>
286 requires detail::InheritsBasicOStream<StreamT>
287StreamT& operator<<(StreamT&& os, unsigned int value)
288{
289 os.put_num(value);
290 return os;
291}
292
293template <class StreamT>
294 requires detail::InheritsBasicOStream<StreamT>
295StreamT& operator<<(StreamT&& os, long value)
296{
297 os.put_num(value);
298 return os;
299}
300
301template <class StreamT>
302 requires detail::InheritsBasicOStream<StreamT>
303StreamT& operator<<(StreamT&& os, unsigned long value)
304{
305 os.put_num(value);
306 return os;
307}
308
309template <class StreamT>
310 requires detail::InheritsBasicOStream<StreamT>
311StreamT& operator<<(StreamT&& os, long long value)
312{
313 os.put_num(value);
314 return os;
315}
316
317template <class StreamT>
318 requires detail::InheritsBasicOStream<StreamT>
319StreamT& operator<<(StreamT&& os, unsigned long long value)
320{
321 os.put_num(value);
322 return os;
323}
324
325template <class StreamT>
326 requires detail::InheritsBasicOStream<StreamT>
327StreamT& operator<<(StreamT&& os, float value)
328{
329 os.put_num(value);
330 return os;
331}
332
333template <class StreamT>
334 requires detail::InheritsBasicOStream<StreamT>
335StreamT& operator<<(StreamT&& os, double value)
336{
337 os.put_num(value);
338 return os;
339}
340
341template <class StreamT>
342 requires detail::InheritsBasicOStream<StreamT>
343StreamT& operator<<(StreamT&& os, long double value)
344{
345 os.put_num(value);
346 return os;
347}
348
349template <class StreamT>
350 requires detail::InheritsBasicOStream<StreamT>
352{
353 os.put_char(ch);
354 return os;
355}
356
357template <class StreamT>
358 requires detail::InheritsBasicOStream<StreamT>
359StreamT& operator<<(StreamT& os, signed char ch)
360{
361 os.put_char(ch);
362 return os;
363}
364
365template <class StreamT>
366 requires detail::InheritsBasicOStream<StreamT>
367StreamT& operator<<(StreamT& os, unsigned char ch)
368{
369 os.put_char(ch);
370 return os;
371}
372
373template <class StreamT>
374 requires detail::InheritsBasicOStream<StreamT>
375StreamT& operator<<(StreamT& os, const char* s)
376{
377 std::size_t len = std::char_traits<char>::length(s);
378 os.put_data(s, len);
379 return os;
380}
381
382template <class StreamT>
383 requires detail::InheritsBasicOStream<StreamT>
384StreamT& operator<<(StreamT& os, const signed char* s)
385{
386 const char* data = std::bit_cast<const char*>(s);
387 std::size_t len = std::char_traits<char>::length(data);
388 os.put_data(data, len);
389 return os;
390}
391
392template <class StreamT>
393 requires detail::InheritsBasicOStream<StreamT>
394StreamT& operator<<(StreamT& os, const unsigned char* s)
395{
396 const char* data = std::bit_cast<const char*>(s);
397 std::size_t len = std::char_traits<char>::length(data);
398 os.put_data(data, len);
399 return os;
400}
401
402template <class StreamT>
403 requires detail::InheritsBasicOStream<StreamT>
404StreamT& operator<<(StreamT& os, const std::string& s)
405{
406 os.put_data(s.data(), s.size());
407 return os;
408}
409
410template <class StreamT>
411 requires detail::InheritsBasicOStream<StreamT>
412StreamT& operator<<(StreamT& os, std::string_view s)
413{
414 os.put_data(s.data(), s.size());
415 return os;
416}
417
418template <class StreamT>
419 requires detail::InheritsBasicOStream<StreamT>
420StreamT& operator<<(StreamT& os, const std::error_code& ec)
421{
422 const char* category = ec.category().name();
423 std::size_t category_len = std::char_traits<char>::length(category);
424
425 os.put_data(category, category_len);
426 os.put_char(':');
427 os.put_num(ec.value());
428
429 return os;
430}
431
432template <class StreamT, class T>
433 requires (detail::InheritsBasicOStream<StreamT> &&
434 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:54
constexpr int dec_digits(ValueT i) noexcept
Definition Utility.hpp:52
constexpr auto bind() noexcept
Definition Slot.hpp:97