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