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]] char* copyTail(char* dst, size_t n) const
307 {
308 assert(n <= sz);
309 ranges::copy(std::span{buf.data() + BUF_SIZE - n, n}, dst);
310 return dst + n;
311 }
312
313 [[nodiscard]] operator std::string() const
314 {
315 return std::string(data(), this->size());
316 }
317
318private:
319 [[nodiscard]] const char* data() const { return buf.data() + BUF_SIZE - sz; }
320 [[nodiscard]] auto end() { return buf.end(); }
321
322private:
323 std::array<char, BUF_SIZE> buf;
324 unsigned char sz;
325};
326
327
328// Format an integral as a hexadecimal value with a fixed number of characters.
329// This fixed width means it either adds leading zeros or truncates the result
330// (it keeps the rightmost digits).
331template<size_t N, std::integral T> struct ConcatFixedWidthHexIntegral
332{
334 : t(t_)
335 {
336 }
337
338 [[nodiscard]] size_t size() const
339 {
340 return N;
341 }
342
343 [[nodiscard]] char* copy(char* dst) const
344 {
345 char* p = dst + N;
346 auto u = static_cast<FastUnsigned<T>>(t);
347
348 repeat(N, [&] {
349 auto d = u & 15;
350 *--p = (d < 10) ? static_cast<char>(d + '0')
351 : static_cast<char>(d - 10 + 'a');
352 u >>= 4;
353 });
354
355 return dst + N;
356 }
357
358private:
359 T t;
360};
361
362// Format an integral as a binary value with a fixed number of characters.
363// This fixed width means it either adds leading zeros or truncates the result
364// (it keeps the rightmost digits).
365template<size_t N, std::integral T> struct ConcatFixedWidthBinIntegral
366{
368 : t(t_)
369 {
370 }
371
372 [[nodiscard]] size_t size() const
373 {
374 return N;
375 }
376
377 [[nodiscard]] char* copy(char* dst) const
378 {
379 char* p = dst + N;
380 auto u = static_cast<FastUnsigned<T>>(t);
381
382 repeat(N, [&] {
383 *--p = static_cast<char>((u & 1) + '0');
384 u >>= 1;
385 });
386
387 return dst + N;
388 }
389
390private:
391 T t;
392};
393
394
395// Prints a number of spaces (without constructing a temporary string).
397{
398 ConcatSpaces(size_t n_)
399 : n(n_)
400 {
401 }
402
403 [[nodiscard]] size_t size() const
404 {
405 return n;
406 }
407
408 [[nodiscard]] char* copy(char* dst) const
409 {
410 ranges::fill(std::span{dst, n}, ' ');
411 return dst + n;
412 }
413
414private:
415 size_t n;
416};
417
418
419// Format an integral as a decimal value with a fixed number of characters.
420// This fixed width means it either adds leading spaces or truncates the result
421// (it keeps the rightmost digits).
422template<size_t N, std::integral T> struct ConcatFixedWidthDecIntegral
423{
425 : helper(t)
426 {
427 }
428
429 [[nodiscard]] size_t size() const
430 {
431 return N;
432 }
433
434 [[nodiscard]] char* copy(char* dst) const
435 {
436 auto n2 = helper.size();
437 if (N <= n2) {
438 return helper.copyTail(dst, N);
439 } else {
440 ConcatSpaces spaces(N - n2);
441 auto* p = spaces.copy(dst);
442 return helper.copy(p);
443 }
444 }
445
446private:
447 ConcatIntegral<T> helper;
448};
449
450
451// Create a 'ConcatUnit<T>' wrapper object for a given 'T' value.
452
453// Generic version: use the corresponding ConcatUnit<T> class. This can be
454// a specialized version for 'T', or the generic (slow) version which uses
455// operator<<(ostream&, T).
456template<typename T>
457[[nodiscard]] inline auto makeConcatUnit(const T& t)
458{
459 return ConcatUnit<T>(t);
460}
461
462// Overloads for various cases (strings, integers, floats, ...).
463[[nodiscard]] inline auto makeConcatUnit(const std::string& s)
464{
466}
467
468[[nodiscard]] inline auto makeConcatUnit(const char* s)
469{
471}
472
473[[nodiscard]] inline auto makeConcatUnit(char* s)
474{
476}
477[[nodiscard]] inline auto makeConcatUnit(const TemporaryString& s)
478{
480}
481[[nodiscard]] inline auto makeConcatUnit(zstring_view s)
482{
484}
485
486// Note: no ConcatIntegral<char> because that is printed as a single character
487[[nodiscard]] inline auto makeConcatUnit(signed char c)
488{
490}
491
492[[nodiscard]] inline auto makeConcatUnit(unsigned char c)
493{
495}
496
497[[nodiscard]] inline auto makeConcatUnit(short s)
498{
499 return ConcatIntegral<short>(s);
500}
501
502[[nodiscard]] inline auto makeConcatUnit(unsigned short s)
503{
505}
506
507[[nodiscard]] inline auto makeConcatUnit(int i)
508{
509 return ConcatIntegral<int>(i);
510}
511
512[[nodiscard]] inline auto makeConcatUnit(unsigned u)
513{
514 return ConcatIntegral<unsigned>(u);
515}
516
517[[nodiscard]] inline auto makeConcatUnit(long l)
518{
519 return ConcatIntegral<long>(l);
520}
521
522[[nodiscard]] inline auto makeConcatUnit(unsigned long l)
523{
525}
526
527[[nodiscard]] inline auto makeConcatUnit(long long l)
528{
530}
531
532[[nodiscard]] inline auto makeConcatUnit(unsigned long long l)
533{
535}
536
537#if 0
538// Converting float->string via std::to_string() might be faster than via
539// std::stringstream. Though the former doesn't seem to work on Dingux??
540//
541// But for openMSX this isn't critical, so we can live with the default
542// (slower?) version.
543
544[[nodiscard]] inline auto makeConcatUnit(float f)
545{
546 return ConcatToString<float>(f);
547}
548
549[[nodiscard]] inline auto makeConcatUnit(double d)
550{
551 return ConcatToString<double>(d);
552}
553
554[[nodiscard]] inline auto makeConcatUnit(long double d)
555{
556 return ConcatToString<long double>(d);
557}
558#endif
559
560template<size_t N, std::integral T>
561[[nodiscard]] inline auto makeConcatUnit(const ConcatFixedWidthHexIntegral<N, T>& t)
562{
563 return t;
564}
565
566template<size_t N, std::integral T>
567[[nodiscard]] inline auto makeConcatUnit(const ConcatFixedWidthBinIntegral<N, T>& t)
568{
569 return t;
570}
571
572template<size_t N, std::integral T>
573[[nodiscard]] inline auto makeConcatUnit(const ConcatFixedWidthDecIntegral<N, T>& t)
574{
575 return t;
576}
577
578[[nodiscard]] inline auto makeConcatUnit(const ConcatSpaces& t)
579{
580 return t;
581}
582
583
584// Calculate the total size for a bunch (a tuple) of ConcatUnit<T> objects.
585// That is, calculate the sum of calling the size() method on each ConcatUnit.
586template<typename Tuple, size_t... Is>
587[[nodiscard]] size_t calcTotalSizeHelper(const Tuple& t, std::index_sequence<Is...>)
588{
589 return (... + std::get<Is>(t).size());
590}
591
592template<typename... Ts>
593[[nodiscard]] size_t calcTotalSize(const std::tuple<Ts...>& t)
594{
595 return calcTotalSizeHelper(t, std::index_sequence_for<Ts...>{});
596}
597
598
599// Copy each ConcatUnit<T> in the given tuple to the final result.
600template<typename Tuple, size_t... Is>
601void copyUnitsHelper(char* dst, const Tuple& t, std::index_sequence<Is...>)
602{
603 auto l = { ((dst = std::get<Is>(t).copy(dst)) , 0)... };
604 (void)l;
605}
606
607template<typename... Ts>
608void copyUnits(char* dst, const std::tuple<Ts...>& t)
609{
610 copyUnitsHelper(dst, t, std::index_sequence_for<Ts...>{});
611}
612
613// Fast integral -> string conversion. (Standalone version, result is not part
614// of a larger string).
615[[nodiscard]] inline std::string to_string(std::integral auto x)
616{
617 return ConcatIntegral(x);
618}
619
620} // namespace strCatImpl
621
622
623// Generic version
624template<typename... Ts>
625[[nodiscard]] std::string strCat(Ts&& ...ts)
626{
627 // Strategy:
628 // - For each parameter (of any type) we create a ConcatUnit object.
629 // - We sum the results of callings size() on all those objects.
630 // - We allocate a string of that total size.
631 // - We copy() each ConcatUnit into that string.
632
633 auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
635 // Ideally we want an uninitialized string with given size, but that's not
636 // yet possible. Though see the following proposal (for c++20):
637 // www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1072r0.html
638 std::string result(size, ' ');
639 char* dst = result.data();
641 return result;
642}
643
644// Optimized strCat() for the case that the 1st parameter is a temporary
645// (rvalue) string. In that case we can append to that temporary.
646template<typename... Ts>
647[[nodiscard]] std::string strCat(std::string&& first, Ts&& ...ts)
648{
649 strAppend(first, std::forward<Ts>(ts)...);
650 return std::move(first);
651}
652
653// Degenerate case
654[[nodiscard]] inline std::string strCat()
655{
656 return {};
657}
658
659// Extra overloads. These don't change and/or extend the above functionality,
660// but in some cases they might improve performance a bit (see notes above
661// about uninitialized string resize). With these overloads strCat()/strAppend()
662// should never be less efficient than a sequence of + or += string-operations.
663[[nodiscard]] inline std::string strCat(const std::string& x) { return x; }
664[[nodiscard]] inline std::string strCat(std::string&& x) { return std::move(x); }
665[[nodiscard]] inline std::string strCat(const char* x) { return {x}; }
666[[nodiscard]] inline std::string strCat(char x) { return std::string(1, x); } // Not: return {1, x};
667[[nodiscard]] inline std::string strCat(std::string_view x) { return {x.data(), x.size()}; }
668
669[[nodiscard]] inline std::string strCat(signed char x) { return strCatImpl::to_string(x); }
670[[nodiscard]] inline std::string strCat(unsigned char x) { return strCatImpl::to_string(x); }
671[[nodiscard]] inline std::string strCat(short x) { return strCatImpl::to_string(x); }
672[[nodiscard]] inline std::string strCat(unsigned short x) { return strCatImpl::to_string(x); }
673[[nodiscard]] inline std::string strCat(int x) { return strCatImpl::to_string(x); }
674[[nodiscard]] inline std::string strCat(unsigned x) { return strCatImpl::to_string(x); }
675[[nodiscard]] inline std::string strCat(long x) { return strCatImpl::to_string(x); }
676[[nodiscard]] inline std::string strCat(unsigned long x) { return strCatImpl::to_string(x); }
677[[nodiscard]] inline std::string strCat(long long x) { return strCatImpl::to_string(x); }
678[[nodiscard]] inline std::string strCat(unsigned long long x) { return strCatImpl::to_string(x); }
679
680[[nodiscard]] inline std::string strCat(const std::string& x, const std::string& y) { return x + y; }
681[[nodiscard]] inline std::string strCat(const char* x, const std::string& y) { return x + y; }
682[[nodiscard]] inline std::string strCat(char x, const std::string& y) { return x + y; }
683[[nodiscard]] inline std::string strCat(const std::string& x, const char* y) { return x + y; }
684[[nodiscard]] inline std::string strCat(const std::string& x, char y) { return x + y; }
685[[nodiscard]] inline std::string strCat(std::string&& x, const std::string& y) { return x + y; }
686[[nodiscard]] inline std::string strCat(const std::string& x, std::string&& y) { return x + y; }
687[[nodiscard]] inline std::string strCat(std::string&& x, std::string&& y) { return x + y; }
688[[nodiscard]] inline std::string strCat(const char* x, std::string&& y) { return x + y; }
689[[nodiscard]] inline std::string strCat(char x, std::string&& y) { return x + y; }
690[[nodiscard]] inline std::string strCat(std::string&& x, const char* y) { return x + y; }
691[[nodiscard]] inline std::string strCat(std::string&& x, char y) { return x + y; }
692
693template<typename... Ts> [[nodiscard]] TemporaryString tmpStrCat(Ts&&... ts)
694{
695 auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
697 return TemporaryString(
698 size, [&](char* dst) { strCatImpl::copyUnits(dst, t); });
699}
700
701// Generic version
702template<typename... Ts>
703void strAppend(std::string& result, Ts&& ...ts)
704{
705 // Implementation strategy is similar to strCat(). Main difference is
706 // that we now extend an existing string instead of creating a new one.
707
708 auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
709 auto extraSize = strCatImpl::calcTotalSize(t);
710 auto oldSize = result.size();
711 result.append(extraSize, ' '); // see note in strCat() about uninitialized string
712 char* dst = &result[oldSize];
714}
715
716// Degenerate case
717inline void strAppend(std::string& /*x*/)
718{
719 // nothing
720}
721
722// Extra overloads, see strCat().
723inline void strAppend(std::string& x, const std::string& y) { x += y; }
724inline void strAppend(std::string& x, const char* y) { x += y; }
725inline void strAppend(std::string& x, std::string_view y) { x.append(y.data(), y.size()); }
726
727
728template<size_t N, std::integral T>
730{
731 return {t};
732}
733
734template<size_t N, std::integral T>
736{
737 return {t};
738}
739
740template<size_t N, std::integral T>
742{
743 return {t};
744}
745
746[[nodiscard]] inline strCatImpl::ConcatSpaces spaces(size_t n)
747{
748 return {n};
749}
750
751#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:305
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:250
STL namespace.
FastUnsigned< T > absHelper(T t)
Definition: strCat.hh:260
auto makeConcatUnit(const T &t)
Definition: strCat.hh:457
void copyUnits(char *dst, const std::tuple< Ts... > &t)
Definition: strCat.hh:608
void copyUnitsHelper(char *dst, const Tuple &t, std::index_sequence< Is... >)
Definition: strCat.hh:601
typename FastUnsignedImpl< T >::type FastUnsigned
Definition: strCat.hh:255
size_t calcTotalSizeHelper(const Tuple &t, std::index_sequence< Is... >)
Definition: strCat.hh:587
size_t calcTotalSize(const std::tuple< Ts... > &t)
Definition: strCat.hh:593
std::string to_string(std::integral auto x)
Definition: strCat.hh:615
size_t size(std::string_view utf8)
strCatImpl::ConcatFixedWidthHexIntegral< N, T > hex_string(T t)
Definition: strCat.hh:729
strCatImpl::ConcatSpaces spaces(size_t n)
Definition: strCat.hh:746
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:693
std::string strCat(Ts &&...ts)
Definition: strCat.hh:625
strCatImpl::ConcatFixedWidthBinIntegral< N, T > bin_string(T t)
Definition: strCat.hh:735
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:703
strCatImpl::ConcatFixedWidthDecIntegral< N, T > dec_string(T t)
Definition: strCat.hh:741
char * copy(char *dst) const
Definition: strCat.hh:377
char * copy(char *dst) const
Definition: strCat.hh:434
char * copy(char *dst) const
Definition: strCat.hh:343
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
char * copyTail(char *dst, size_t n) const
Definition: strCat.hh:306
static constexpr bool IS_SIGNED
Definition: strCat.hh:276
char * copy(char *dst) const
Definition: strCat.hh:408
ConcatSpaces(size_t n_)
Definition: strCat.hh:398
size_t size() const
Definition: strCat.hh:403
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