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 <system_error>
23#include <type_traits>
24#include <string>
25#include <string_view>
26#include <cassert>
27#include <limits>
28
29#include "TypeTraits.hpp"
30
31namespace toolbox {
32inline namespace util {
33
34/*
35DerivedT is required to implement the following:
36
371) char* do_prepare_space(std::size_t num_bytes)
38 - returns writable buffer of atleast `num_bytes` bytes for immediate writing
39 - will be followed by a call to `do_relinquish_space` consuming up to `num_bytes`
40
412) void do_relinquish_space(std::size_t consumed_num_bytes)
42 - data of size `consumed_num_bytes` was written into given buffer (by `do_prepare_space`)
43 - The remaining unconsumed bytes are relinquished back to derived class
44
453) void do_set_badbit()
46 - informs derived type of failure when outputting, specifically invoked on:
47 - failure to allocate space
48 - failure to output
49*/
50
51template <class DerivedT>
52class OStreamBase;
53
54namespace detail {
55template <class T>
57
58template <class T>
59concept AllowedIntegral = std::is_integral_v<T> && !ExcludedIntegral<T>;
60
61template <class T>
62concept AllowedNumeric = AllowedIntegral<T> || std::floating_point<T>;
63
64template <class T>
66
67template <class T>
69 std::is_base_of_v<OStreamBase<std::remove_cvref_t<T>>, std::remove_cvref_t<T>>;
70
71} // namespace detail
72
73template <class DerivedT>
75 public:
76 DerivedT& put_data(const char* data, std::size_t data_size);
77
78 template <class T>
81
83
84 template <class T>
85 requires (detail::AllowedIntegral<T> && !std::same_as<T, bool>)
87
88 template <class T>
89 requires std::floating_point<T>
91
93 DerivedT& put(char ch);
94 DerivedT& write(const char* data, std::size_t sz);
95
96 protected:
97 // A number requires exactly N bytes to print, however put_num() will request a larger buffer
98 // size for performance reasons (ofcourse, when the buffer is relinquished it will correctly
99 // indicated that N bytes are consumed)
100 // However, if derived class cannot fullfill this request, then will fallback to slower path
101 // that requests exactly the N bytes required to print the number.
102 static constexpr std::size_t PutNumMaxBufRequest = 32u;
103
104 private:
105 DerivedT& get_derived() noexcept;
106
107 char* prepare_space(std::size_t num_bytes);
108 void relinquish_space(std::size_t consumed_num_bytes);
109 void set_badbit();
110};
111
113DerivedT& OStreamBase<DerivedT>::get_derived() noexcept
114{
115 // sizeof(T) results in a compiler error if T is an incomplete type
116 static_assert(sizeof(DerivedT) >= 0, "incomplete derived type");
117 static_assert(std::is_base_of_v<OStreamBase<DerivedT>, DerivedT>);
118 return static_cast<DerivedT&>(*this);
119}
120
121template <class DerivedT>
123{
124 return get_derived().do_prepare_space(num_bytes);
125}
126
127template <class DerivedT>
129{
130 return get_derived().do_relinquish_space(consumed_num_bytes);
131}
132
133template <class DerivedT>
134void OStreamBase<DerivedT>::set_badbit()
135{
136 get_derived().do_set_badbit();
137}
138
139template <class DerivedT>
141{
142 char* buf = prepare_space(data_size);
143 if (buf != nullptr) [[likely]] {
144 std::copy(data, data + data_size, buf);
145 relinquish_space(data_size);
146 } else {
147 set_badbit();
148 }
149 return get_derived();
150}
151
152template <class DerivedT>
153template <class T> requires detail::AllowedChar<T>
155{
156 char* buf = prepare_space(1);
157 if (buf != nullptr) [[likely]] {
158 *buf = ch;
159 relinquish_space(1);
160 } else {
161 set_badbit();
162 }
163 return get_derived();
164}
165
166template <class DerivedT>
168{
169 put_char(val ? '1' : '0');
170 return get_derived();
171}
172
173template <class DerivedT>
174template <class T> requires (detail::AllowedIntegral<T> && !std::same_as<T, bool>)
176{
177 constexpr std::size_t MaxBytesNeeded
178 = dec_digits(std::numeric_limits<T>::max())
179 + std::is_signed_v<T>;
180
181 static_assert(MaxBytesNeeded <= PutNumMaxBufRequest);
182
183 char* buf = prepare_space(MaxBytesNeeded);
184 std::size_t buf_sz = MaxBytesNeeded;
185
186 if (buf == nullptr) [[unlikely]] {
187 // slightly slower path
188 auto digits = dec_digits(val);
189 if constexpr (std::is_signed_v<T>) {
190 digits += (val < 0);
191 }
192 buf = prepare_space(digits);
193 buf_sz = digits;
194 }
195
196 if (buf != nullptr) [[likely]] {
197 // impossible for it to fail (N.B. to_chars is non-throwing)
198 const auto [end, ec] = std::to_chars(buf, buf + buf_sz, val);
199 assert(ec == std::errc());
200 relinquish_space(end - buf);
201 } else {
202 set_badbit();
203 }
204
205 return get_derived();
206}
207
208template <class DerivedT>
209template <class T> requires std::floating_point<T>
211{
212 char* buf = prepare_space(PutNumMaxBufRequest);
213 if (buf != nullptr) [[likely]] { // fast path
214 // impossible for to_chars to fail (N.B. to_chars is non-throwing)
215 const auto [end, ec] = std::to_chars(buf, buf + PutNumMaxBufRequest, val);
216 relinquish_space(end - buf);
217 assert(ec == std::errc());
218 }
219 else {
220 // slower path
221 // impossible for to_chars to fail (N.B. to_chars is non-throwing)
223 const auto [end, ec] = std::to_chars(local_space, local_space + PutNumMaxBufRequest, val);
224 assert(ec == std::errc());
225
226 std::size_t consumed = end - local_space;
227 buf = prepare_space(consumed);
228
229 if (buf != nullptr) [[likely]] {
230 std::copy(local_space, end, buf);
231 relinquish_space(consumed);
232 } else {
233 set_badbit();
234 }
235 }
236
237 return get_derived();
238}
239
240template <class DerivedT>
242{
243 return put_char(ch);
244}
245
246template <class DerivedT>
248{
249 return put_data(data, sz);
250}
251
252template <class StreamT>
254StreamT& operator<<(StreamT&& os, bool value)
255{
256 os.put_num(value);
257 return os;
258}
259
260template <class StreamT>
261 requires detail::InheritsBasicOStream<StreamT>
262StreamT& operator<<(StreamT&& os, short value)
263{
264 os.put_num(value);
265 return os;
266}
267
268template <class StreamT>
269 requires detail::InheritsBasicOStream<StreamT>
270StreamT& operator<<(StreamT&& os, unsigned short value)
271{
272 os.put_num(value);
273 return os;
274}
275
276template <class StreamT>
277 requires detail::InheritsBasicOStream<StreamT>
278StreamT& operator<<(StreamT&& os, int value)
279{
280 os.put_num(value);
281 return os;
282}
283
284template <class StreamT>
285 requires detail::InheritsBasicOStream<StreamT>
286StreamT& operator<<(StreamT&& os, unsigned int value)
287{
288 os.put_num(value);
289 return os;
290}
291
292template <class StreamT>
293 requires detail::InheritsBasicOStream<StreamT>
294StreamT& operator<<(StreamT&& os, long value)
295{
296 os.put_num(value);
297 return os;
298}
299
300template <class StreamT>
301 requires detail::InheritsBasicOStream<StreamT>
302StreamT& operator<<(StreamT&& os, unsigned long value)
303{
304 os.put_num(value);
305 return os;
306}
307
308template <class StreamT>
309 requires detail::InheritsBasicOStream<StreamT>
310StreamT& operator<<(StreamT&& os, long long value)
311{
312 os.put_num(value);
313 return os;
314}
315
316template <class StreamT>
317 requires detail::InheritsBasicOStream<StreamT>
318StreamT& operator<<(StreamT&& os, unsigned long long value)
319{
320 os.put_num(value);
321 return os;
322}
323
324template <class StreamT>
325 requires detail::InheritsBasicOStream<StreamT>
326StreamT& operator<<(StreamT&& os, float value)
327{
328 os.put_num(value);
329 return os;
330}
331
332template <class StreamT>
333 requires detail::InheritsBasicOStream<StreamT>
334StreamT& operator<<(StreamT&& os, double value)
335{
336 os.put_num(value);
337 return os;
338}
339
340template <class StreamT>
341 requires detail::InheritsBasicOStream<StreamT>
342StreamT& operator<<(StreamT&& os, long double value)
343{
344 os.put_num(value);
345 return os;
346}
347
348template <class StreamT>
349 requires detail::InheritsBasicOStream<StreamT>
351{
352 os.put_char(ch);
353 return os;
354}
355
356template <class StreamT>
357 requires detail::InheritsBasicOStream<StreamT>
358StreamT& operator<<(StreamT& os, signed char ch)
359{
360 os.put_char(ch);
361 return os;
362}
363
364template <class StreamT>
365 requires detail::InheritsBasicOStream<StreamT>
366StreamT& operator<<(StreamT& os, unsigned char ch)
367{
368 os.put_char(ch);
369 return os;
370}
371
372template <class StreamT>
373 requires detail::InheritsBasicOStream<StreamT>
374StreamT& operator<<(StreamT& os, const char* s)
375{
376 std::size_t len = std::char_traits<char>::length(s);
377 os.put_data(s, len);
378 return os;
379}
380
381template <class StreamT>
382 requires detail::InheritsBasicOStream<StreamT>
383StreamT& operator<<(StreamT& os, const signed char* s)
384{
385 const char* data = std::bit_cast<const char*>(s);
386 std::size_t len = std::char_traits<char>::length(data);
387 os.put_data(data, len);
388 return os;
389}
390
391template <class StreamT>
392 requires detail::InheritsBasicOStream<StreamT>
393StreamT& operator<<(StreamT& os, const unsigned char* s)
394{
395 const char* data = std::bit_cast<const char*>(s);
396 std::size_t len = std::char_traits<char>::length(data);
397 os.put_data(data, len);
398 return os;
399}
400
401template <class StreamT>
402 requires detail::InheritsBasicOStream<StreamT>
403StreamT& operator<<(StreamT& os, const std::string& s)
404{
405 os.put_data(s.data(), s.size());
406 return os;
407}
408
409template <class StreamT>
410 requires detail::InheritsBasicOStream<StreamT>
411StreamT& operator<<(StreamT& os, std::string_view s)
412{
413 os.put_data(s.data(), s.size());
414 return os;
415}
416
417template <class StreamT>
418 requires detail::InheritsBasicOStream<StreamT>
419StreamT& operator<<(StreamT& os, const std::error_code& ec)
420{
421 const char* category = ec.category().name();
422 std::size_t category_len = std::char_traits<char>::length(category);
423
424 os.put_data(category, category_len);
425 os.put_char(':');
426 os.put_num(ec.value());
427
428 return os;
429}
430
431template <class StreamT, class T>
432 requires (detail::InheritsBasicOStream<StreamT> &&
433 std::is_rvalue_reference_v<StreamT&&>)
434StreamT&& operator<<(StreamT&& os, const T& value)
435{
436 os << value;
437 return static_cast<StreamT&&>(os);
438}
439
440} // namespace util
441} // namespace toolbox
442
443#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