openMSX
strCat.hh
Go to the documentation of this file.
1#ifndef STRCAT_HH
2#define STRCAT_HH
3
4#include "TemporaryString.hh"
5#include "ranges.hh"
6#include "xrange.hh"
7#include "zstring_view.hh"
8#include <array>
9#include <climits>
10#include <limits>
11#include <span>
12#include <sstream>
13#include <string>
14#include <string_view>
15#include <tuple>
16#include <utility>
17
18// strCat and strAppend()
19//
20// Inspired by google's absl::StrCat (similar interface, different implementation).
21// See https://abseil.io/blog/20171023-cppcon-strcat
22// and https://github.com/abseil/abseil-cpp/blob/master/absl/strings/str_cat.h
23
24
25// --- Public interface ---
26
27// Concatenate a bunch of 'printable' objects.
28//
29// Conceptually each object is converted to a string, all those strings are
30// concatenated, and that result is returned.
31//
32// For example:
33// auto s = strCat("foobar", s, ' ', 123);
34// is equivalent to
35// auto s = "foobar" + s + ' ' + std::to_string(123);
36//
37// The former executes faster than the latter because (among other things) it
38// immediately creates a result of the correct size instead of (re)allocating
39// all intermediate strings. Also it doesn't create temporary string objects
40// for the integer conversions.
41template<typename... Ts>
42[[nodiscard]] std::string strCat(Ts&& ...ts);
43
44// Consider using 'tmpStrCat()' as an alternative for 'strCat()'. The only
45// difference is that this one returns a 'TemporaryString' instead of a
46// 'std::string'. This can be faster (e.g. no heap allocation) when the result
47// is not required to be a 'std::string' (std::string_view is sufficient) and
48// when it really is a temporary (short lived) string.
49template<typename... Ts>
50[[nodiscard]] TemporaryString tmpStrCat(Ts&&... ts);
51
52// Append a bunch of 'printable' objects to an exiting string.
53//
54// Can be used to optimize
55// s += strCat(a, b, c);
56// to
57// strAppend(s, a, b, c);
58//
59// This latter will immediately construct the result in 's' instead of first
60// formatting to a temporary string followed by appending that temporary to 's'.
61//
62// There's one limitation though: it's not allowed to append a string to
63// itself, not even parts of the string (strCat() doesn't have this
64// limitation). So the following is invalid:
65// strAppend(s, s); // INVALID, append s to itself
66//
67// string_view v = s;
68// v.substr(10, 20);
69// strAppend(s, v); // INVALID, append part of s to itself
70template<typename... Ts>
71void strAppend(std::string& result, Ts&& ...ts);
72
73
74// Format an integer as a fixed-width hexadecimal value and insert it into
75// a strCat() or strAppend() sequence.
76// - If the value is small, leading zeros are printed.
77// - If the value is too big, it gets truncated. Only the rightmost characters
78// are kept.
79//
80// For example:
81// s = strCat("The value is: 0x", hex_string<4>(value), '.');
84
85
86// Insert 'n' spaces into a strCat() or strAppend() sequence.
87//
88// For example:
89// s = strCat("The result is ", spaces(30 - item.size()), item);
90//
91// This can be more efficient than creating a temporary string containing 'n'
92// spaces, like this
93// s = strCat("The result is ", std::string(30 - item.size(), ' '), item);
95
96
97// --- Implementation details ---
98
99namespace strCatImpl {
100
101// ConcatUnit
102// These implement various mechanisms to concatenate an object to a string.
103// All these classes implement:
104//
105// - size_t size() const;
106// Returns the (exact) size in characters of the formatted object.
107//
108// - char* copy(char* dst) const;
109// Copy the formatted object to 'dst', returns an updated pointer.
110template<typename T> struct ConcatUnit;
111
112
113// Helper for types which are formatted via a temporary string object
115{
116 ConcatViaString(std::string s_)
117 : s(std::move(s_))
118 {
119 }
120
121 [[nodiscard]] size_t size() const
122 {
123 return s.size();
124 }
125
126 [[nodiscard]] char* copy(char* dst) const
127 {
128 ranges::copy(s, dst);
129 return dst + s.size();
130 }
131
132private:
133 std::string s;
134};
135
136#if 0
137// Dingux doesn't have std::to_string() ???
138
139// Helper for types which are printed via std::to_string(),
140// e.g. floating point types.
141template<typename T>
142struct ConcatToString : ConcatViaString
143{
144 ConcatToString(T t)
145 : ConcatViaString(std::to_string(t))
146 {
147 }
148};
149#endif
150
151// The default (slow) implementation uses 'operator<<(ostream&, T)'
152template<typename T>
154{
155 ConcatUnit(const T& t)
156 : ConcatViaString([&](){
157 std::ostringstream os;
158 os << t;
159 return os.str();
160 }())
161 {
162 }
163};
164
165
166// ConcatUnit<string_view>:
167// store the string view (copies the view, not the string)
168template<> struct ConcatUnit<std::string_view>
169{
170 ConcatUnit(const std::string_view v_)
171 : v(v_)
172 {
173 }
174
175 [[nodiscard]] size_t size() const
176 {
177 return v.size();
178 }
179
180 [[nodiscard]] char* copy(char* dst) const
181 {
182 ranges::copy(v, dst);
183 return dst + v.size();
184 }
185
186private:
187 std::string_view v;
188};
189
190
191// ConcatUnit<char>:
192// store single char (length is constant 1)
193template<> struct ConcatUnit<char>
194{
195 ConcatUnit(char c_)
196 : c(c_)
197 {
198 }
199
200 [[nodiscard]] size_t size() const
201 {
202 return 1;
203 }
204
205 [[nodiscard]] char* copy(char* dst) const
206 {
207 *dst = c;
208 return dst + 1;
209 }
210
211private:
212 char c;
213};
214
215
216// ConcatUnit<bool>:
217// store bool (length is constant 1)
218template<> struct ConcatUnit<bool>
219{
220 ConcatUnit(bool b_)
221 : b(b_)
222 {
223 }
224
225 [[nodiscard]] size_t size() const
226 {
227 return 1;
228 }
229
230 [[nodiscard]] char* copy(char* dst) const
231 {
232 *dst = b ? '1' : '0';
233 return dst + 1;
234 }
235
236private:
237 bool b;
238};
239
240
241// Helper classes/functions for fast integer -> string conversion.
242
243// Returns a fast type that is wide enough to hold the absolute value for
244// values of the given type.
245//
246// On x86 32-bit operations are faster than e.g. 16-bit operations.
247// So this function returns a 32-bit type unless 64-bit are needed.
248//
249// 'long' is special because it's either 32-bit (windows) or 64-bit (Linux).
250template<typename T> struct FastUnsignedImpl { using type = unsigned; };
251template<> struct FastUnsignedImpl< long> { using type = unsigned long; };
252template<> struct FastUnsignedImpl<unsigned long> { using type = unsigned long; };
253template<> struct FastUnsignedImpl< long long> { using type = unsigned long long; };
254template<> struct FastUnsignedImpl<unsigned long long> { using type = unsigned long long; };
255template<typename T> using FastUnsigned = typename FastUnsignedImpl<T>::type;
256
257// Helper function to take the absolute value of a signed or unsigned type.
258// (without compiler warning on 't < 0' and '-t' when t is unsigned)
259template<std::unsigned_integral T>
260[[nodiscard]] FastUnsigned<T> absHelper(T t) { return t; }
261
262template<std::signed_integral T>
263[[nodiscard]] FastUnsigned<T> absHelper(T t) { return (t < 0)? -t : t; }
264
265
266// Optimized integer printing.
267//
268// Prints the value to an internal buffer. The formatted characters are
269// generated from back to front. This means the final result is not aligned
270// with the start of this internal buffer.
271//
272// Next to the internal buffer we also store the size (in characters) of the
273// result. This size can be used to calculate the start position in the buffer.
274template<std::integral T> struct ConcatIntegral
275{
276 static constexpr bool IS_SIGNED = std::numeric_limits<T>::is_signed;
277 static constexpr size_t BUF_SIZE = 1 + std::numeric_limits<T>::digits10 + IS_SIGNED;
278
280 {
281 auto p = this->end();
282 std::unsigned_integral auto a = absHelper(t);
283
284 do {
285 *--p = static_cast<char>('0' + (a % 10));
286 a /= 10;
287 } while (a);
288
289 if constexpr (IS_SIGNED) {
290 if (t < 0) *--p = '-';
291 }
292 this->sz = static_cast<unsigned char>(this->end() - p);
293 }
294
295 [[nodiscard]] size_t size() const
296 {
297 return sz;
298 }
299
300 [[nodiscard]] char* copy(char* dst) const
301 {
302 ranges::copy(std::span{data(), sz}, dst);
303 return dst + sz;
304 }
305
306 [[nodiscard]] operator std::string() const
307 {
308 return std::string(data(), this->size());
309 }
310
311private:
312 [[nodiscard]] const char* data() const { return buf.data() + BUF_SIZE - sz; }
313 [[nodiscard]] auto end() { return buf.end(); }
314
315private:
316 std::array<char, BUF_SIZE> buf;
317 unsigned char sz;
318};
319
320
321// Format an integral as a hexadecimal value with a fixed number of characters.
322// This fixed width means it either adds leading zeros or truncates the result
323// (it keeps the rightmost digits).
324template<size_t N, std::integral T> struct ConcatFixedWidthHexIntegral
325{
327 : t(t_)
328 {
329 }
330
331 [[nodiscard]] size_t size() const
332 {
333 return N;
334 }
335
336 [[nodiscard]] char* copy(char* dst) const
337 {
338 char* p = dst + N;
339 auto u = static_cast<FastUnsigned<T>>(t);
340
341 repeat(N, [&] {
342 auto d = u & 15;
343 *--p = (d < 10) ? static_cast<char>(d + '0')
344 : static_cast<char>(d - 10 + 'a');
345 u >>= 4;
346 });
347
348 return dst + N;
349 }
350
351private:
352 T t;
353};
354
355
356// Prints a number of spaces (without constructing a temporary string).
358{
359 ConcatSpaces(size_t n_)
360 : n(n_)
361 {
362 }
363
364 [[nodiscard]] size_t size() const
365 {
366 return n;
367 }
368
369 [[nodiscard]] char* copy(char* dst) const
370 {
371 ranges::fill(std::span{dst, n}, ' ');
372 return dst + n;
373 }
374
375private:
376 size_t n;
377};
378
379
380// Create a 'ConcatUnit<T>' wrapper object for a given 'T' value.
381
382// Generic version: use the corresponding ConcatUnit<T> class. This can be
383// a specialized version for 'T', or the generic (slow) version which uses
384// operator<<(ostream&, T).
385template<typename T>
386[[nodiscard]] inline auto makeConcatUnit(const T& t)
387{
388 return ConcatUnit<T>(t);
389}
390
391// Overloads for various cases (strings, integers, floats, ...).
392[[nodiscard]] inline auto makeConcatUnit(const std::string& s)
393{
395}
396
397[[nodiscard]] inline auto makeConcatUnit(const char* s)
398{
400}
401
402[[nodiscard]] inline auto makeConcatUnit(char* s)
403{
405}
406[[nodiscard]] inline auto makeConcatUnit(const TemporaryString& s)
407{
409}
410[[nodiscard]] inline auto makeConcatUnit(zstring_view s)
411{
413}
414
415// Note: no ConcatIntegral<char> because that is printed as a single character
416[[nodiscard]] inline auto makeConcatUnit(signed char c)
417{
419}
420
421[[nodiscard]] inline auto makeConcatUnit(unsigned char c)
422{
424}
425
426[[nodiscard]] inline auto makeConcatUnit(short s)
427{
428 return ConcatIntegral<short>(s);
429}
430
431[[nodiscard]] inline auto makeConcatUnit(unsigned short s)
432{
434}
435
436[[nodiscard]] inline auto makeConcatUnit(int i)
437{
438 return ConcatIntegral<int>(i);
439}
440
441[[nodiscard]] inline auto makeConcatUnit(unsigned u)
442{
443 return ConcatIntegral<unsigned>(u);
444}
445
446[[nodiscard]] inline auto makeConcatUnit(long l)
447{
448 return ConcatIntegral<long>(l);
449}
450
451[[nodiscard]] inline auto makeConcatUnit(unsigned long l)
452{
454}
455
456[[nodiscard]] inline auto makeConcatUnit(long long l)
457{
459}
460
461[[nodiscard]] inline auto makeConcatUnit(unsigned long long l)
462{
464}
465
466#if 0
467// Converting float->string via std::to_string() might be faster than via
468// std::stringstream. Though the former doesn't seem to work on Dingux??
469//
470// But for openMSX this isn't critical, so we can live with the default
471// (slower?) version.
472
473[[nodiscard]] inline auto makeConcatUnit(float f)
474{
475 return ConcatToString<float>(f);
476}
477
478[[nodiscard]] inline auto makeConcatUnit(double d)
479{
480 return ConcatToString<double>(d);
481}
482
483[[nodiscard]] inline auto makeConcatUnit(long double d)
484{
485 return ConcatToString<long double>(d);
486}
487#endif
488
489template<size_t N, std::integral T>
490[[nodiscard]] inline auto makeConcatUnit(const ConcatFixedWidthHexIntegral<N, T>& t)
491{
492 return t;
493}
494
495[[nodiscard]] inline auto makeConcatUnit(const ConcatSpaces& t)
496{
497 return t;
498}
499
500
501// Calculate the total size for a bunch (a tuple) of ConcatUnit<T> objects.
502// That is, calculate the sum of calling the size() method on each ConcatUnit.
503template<typename Tuple, size_t... Is>
504[[nodiscard]] size_t calcTotalSizeHelper(const Tuple& t, std::index_sequence<Is...>)
505{
506 return (... + std::get<Is>(t).size());
507}
508
509template<typename... Ts>
510[[nodiscard]] size_t calcTotalSize(const std::tuple<Ts...>& t)
511{
512 return calcTotalSizeHelper(t, std::index_sequence_for<Ts...>{});
513}
514
515
516// Copy each ConcatUnit<T> in the given tuple to the final result.
517template<typename Tuple, size_t... Is>
518void copyUnitsHelper(char* dst, const Tuple& t, std::index_sequence<Is...>)
519{
520 auto l = { ((dst = std::get<Is>(t).copy(dst)) , 0)... };
521 (void)l;
522}
523
524template<typename... Ts>
525void copyUnits(char* dst, const std::tuple<Ts...>& t)
526{
527 copyUnitsHelper(dst, t, std::index_sequence_for<Ts...>{});
528}
529
530// Fast integral -> string conversion. (Standalone version, result is not part
531// of a larger string).
532[[nodiscard]] inline std::string to_string(std::integral auto x)
533{
534 return ConcatIntegral(x);
535}
536
537} // namespace strCatImpl
538
539
540// Generic version
541template<typename... Ts>
542[[nodiscard]] std::string strCat(Ts&& ...ts)
543{
544 // Strategy:
545 // - For each parameter (of any type) we create a ConcatUnit object.
546 // - We sum the results of callings size() on all those objects.
547 // - We allocate a string of that total size.
548 // - We copy() each ConcatUnit into that string.
549
550 auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
552 // Ideally we want an uninitialized string with given size, but that's not
553 // yet possible. Though see the following proposal (for c++20):
554 // www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1072r0.html
555 std::string result(size, ' ');
556 char* dst = result.data();
558 return result;
559}
560
561// Optimized strCat() for the case that the 1st parameter is a temporary
562// (rvalue) string. In that case we can append to that temporary.
563template<typename... Ts>
564[[nodiscard]] std::string strCat(std::string&& first, Ts&& ...ts)
565{
566 strAppend(first, std::forward<Ts>(ts)...);
567 return std::move(first);
568}
569
570// Degenerate case
571[[nodiscard]] inline std::string strCat()
572{
573 return {};
574}
575
576// Extra overloads. These don't change and/or extend the above functionality,
577// but in some cases they might improve performance a bit (see notes above
578// about uninitialized string resize). With these overloads strCat()/strAppend()
579// should never be less efficient than a sequence of + or += string-operations.
580[[nodiscard]] inline std::string strCat(const std::string& x) { return x; }
581[[nodiscard]] inline std::string strCat(std::string&& x) { return std::move(x); }
582[[nodiscard]] inline std::string strCat(const char* x) { return {x}; }
583[[nodiscard]] inline std::string strCat(char x) { return std::string(1, x); } // Not: return {1, x};
584[[nodiscard]] inline std::string strCat(std::string_view x) { return {x.data(), x.size()}; }
585
586[[nodiscard]] inline std::string strCat(signed char x) { return strCatImpl::to_string(x); }
587[[nodiscard]] inline std::string strCat(unsigned char x) { return strCatImpl::to_string(x); }
588[[nodiscard]] inline std::string strCat(short x) { return strCatImpl::to_string(x); }
589[[nodiscard]] inline std::string strCat(unsigned short x) { return strCatImpl::to_string(x); }
590[[nodiscard]] inline std::string strCat(int x) { return strCatImpl::to_string(x); }
591[[nodiscard]] inline std::string strCat(unsigned x) { return strCatImpl::to_string(x); }
592[[nodiscard]] inline std::string strCat(long x) { return strCatImpl::to_string(x); }
593[[nodiscard]] inline std::string strCat(unsigned long x) { return strCatImpl::to_string(x); }
594[[nodiscard]] inline std::string strCat(long long x) { return strCatImpl::to_string(x); }
595[[nodiscard]] inline std::string strCat(unsigned long long x) { return strCatImpl::to_string(x); }
596
597[[nodiscard]] inline std::string strCat(const std::string& x, const std::string& y) { return x + y; }
598[[nodiscard]] inline std::string strCat(const char* x, const std::string& y) { return x + y; }
599[[nodiscard]] inline std::string strCat(char x, const std::string& y) { return x + y; }
600[[nodiscard]] inline std::string strCat(const std::string& x, const char* y) { return x + y; }
601[[nodiscard]] inline std::string strCat(const std::string& x, char y) { return x + y; }
602[[nodiscard]] inline std::string strCat(std::string&& x, const std::string& y) { return x + y; }
603[[nodiscard]] inline std::string strCat(const std::string& x, std::string&& y) { return x + y; }
604[[nodiscard]] inline std::string strCat(std::string&& x, std::string&& y) { return x + y; }
605[[nodiscard]] inline std::string strCat(const char* x, std::string&& y) { return x + y; }
606[[nodiscard]] inline std::string strCat(char x, std::string&& y) { return x + y; }
607[[nodiscard]] inline std::string strCat(std::string&& x, const char* y) { return x + y; }
608[[nodiscard]] inline std::string strCat(std::string&& x, char y) { return x + y; }
609
610template<typename... Ts> [[nodiscard]] TemporaryString tmpStrCat(Ts&&... ts)
611{
612 auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
614 return TemporaryString(
615 size, [&](char* dst) { strCatImpl::copyUnits(dst, t); });
616}
617
618// Generic version
619template<typename... Ts>
620void strAppend(std::string& result, Ts&& ...ts)
621{
622 // Implementation strategy is similar to strCat(). Main difference is
623 // that we now extend an existing string instead of creating a new one.
624
625 auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
626 auto extraSize = strCatImpl::calcTotalSize(t);
627 auto oldSize = result.size();
628 result.append(extraSize, ' '); // see note in strCat() about uninitialized string
629 char* dst = &result[oldSize];
631}
632
633// Degenerate case
634inline void strAppend(std::string& /*x*/)
635{
636 // nothing
637}
638
639// Extra overloads, see strCat().
640inline void strAppend(std::string& x, const std::string& y) { x += y; }
641inline void strAppend(std::string& x, const char* y) { x += y; }
642inline void strAppend(std::string& x, std::string_view y) { x.append(y.data(), y.size()); }
643
644
645template<size_t N, std::integral T>
647{
648 return {t};
649}
650
651[[nodiscard]] inline strCatImpl::ConcatSpaces spaces(size_t n)
652{
653 return {n};
654}
655
656#endif
TclObject t
TemporaryString.
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
Definition: zstring_view.hh:22
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:232
STL namespace.
FastUnsigned< T > absHelper(T t)
Definition: strCat.hh:260
auto makeConcatUnit(const T &t)
Definition: strCat.hh:386
void copyUnits(char *dst, const std::tuple< Ts... > &t)
Definition: strCat.hh:525
void copyUnitsHelper(char *dst, const Tuple &t, std::index_sequence< Is... >)
Definition: strCat.hh:518
typename FastUnsignedImpl< T >::type FastUnsigned
Definition: strCat.hh:255
size_t calcTotalSizeHelper(const Tuple &t, std::index_sequence< Is... >)
Definition: strCat.hh:504
size_t calcTotalSize(const std::tuple< Ts... > &t)
Definition: strCat.hh:510
std::string to_string(std::integral auto x)
Definition: strCat.hh:532
size_t size(std::string_view utf8)
strCatImpl::ConcatFixedWidthHexIntegral< N, T > hex_string(T t)
Definition: strCat.hh:646
strCatImpl::ConcatSpaces spaces(size_t n)
Definition: strCat.hh:651
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:610
std::string strCat(Ts &&...ts)
Definition: strCat.hh:542
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:620
char * copy(char *dst) const
Definition: strCat.hh:336
size_t size() const
Definition: strCat.hh:295
char * copy(char *dst) const
Definition: strCat.hh:300
static constexpr size_t BUF_SIZE
Definition: strCat.hh:277
static constexpr bool IS_SIGNED
Definition: strCat.hh:276
char * copy(char *dst) const
Definition: strCat.hh:369
ConcatSpaces(size_t n_)
Definition: strCat.hh:359
size_t size() const
Definition: strCat.hh:364
char * copy(char *dst) const
Definition: strCat.hh:230
char * copy(char *dst) const
Definition: strCat.hh:205
ConcatUnit(const std::string_view v_)
Definition: strCat.hh:170
ConcatUnit(const T &t)
Definition: strCat.hh:155
char * copy(char *dst) const
Definition: strCat.hh:126
size_t size() const
Definition: strCat.hh:121
ConcatViaString(std::string s_)
Definition: strCat.hh:116
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition: xrange.hh:147