OXYGENHUB
◆ main openmsx / src / utils / strCat.hh
30.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef STRCAT_HH
2
#define STRCAT_HH
3
4
#include "TemporaryString.hh"
5
#include "xrange.hh"
6
#include "zstring_view.hh"
7
8
#include <algorithm>
9
#include <array>
10
#include <cstdint>
11
#include <limits>
12
#include <optional>
13
#include <span>
14
#include <string>
15
#include <string_view>
16
#include <functional>
17
#include <type_traits>
18
#include <tuple>
19
#include <utility>
20
21
// Suppress false positive -Wstringop-overflow warnings in GCC for template instantiations in this header.
22
// No push/pop because the warnings come from template instantiations inside this header itself.
23
#if defined(__GNUC__) && !defined(__clang__)
24
#pragma GCC diagnostic ignored "-Wstringop-overflow"
25
#pragma GCC diagnostic ignored "-Warray-bounds"
26
#endif
27
28
// strCat and strAppend()
29
//
30
// Inspired by google's absl::StrCat (similar interface, different implementation).
31
// See https://abseil.io/blog/20171023-cppcon-strcat
32
// and https://github.com/abseil/abseil-cpp/blob/master/absl/strings/str_cat.h
33
34
35
// --- Public interface ---
36
37
// Concatenate a bunch of 'printable' objects.
38
//
39
// Conceptually each object is converted to a string, all those strings are
40
// concatenated, and that result is returned.
41
//
42
// For example:
43
//      auto s = strCat("foobar", s, ' ', 123);
44
// is equivalent to
45
//      auto s = "foobar" + s + ' ' + std::to_string(123);
46
//
47
// The former executes faster than the latter because (among other things) it
48
// immediately creates a result of the correct size instead of (re)allocating
49
// all intermediate strings. Also it doesn't create temporary string objects
50
// for the integer conversions.
51
template<typename... Ts>
52
[[nodiscard]] std::string strCat(Ts&& ...ts);
53
54
// Consider using 'tmpStrCat()' as an alternative for 'strCat()'. The only
55
// difference is that this one returns a 'TemporaryString' instead of a
56
// 'std::string'. This can be faster (e.g. no heap allocation) when the result
57
// is not required to be a 'std::string' (std::string_view is sufficient) and
58
// when it really is a temporary (short lived) string.
59
template<typename... Ts>
60
[[nodiscard]] TemporaryString tmpStrCat(Ts&&... ts);
61
62
// Append a bunch of 'printable' objects to an exiting string.
63
//
64
// Can be used to optimize
65
//     s += strCat(a, b, c);
66
// to
67
//     strAppend(s, a, b, c);
68
//
69
// This latter will immediately construct the result in 's' instead of first
70
// formatting to a temporary string followed by appending that temporary to 's'.
71
//
72
// There's one limitation though: it's not allowed to append a string to
73
// itself, not even parts of the string (strCat() doesn't have this
74
// limitation). So the following is invalid:
75
//     strAppend(s, s); // INVALID, append s to itself
76
//
77
//     string_view v = s;
78
//     v.substr(10, 20);
79
//     strAppend(s, v); // INVALID, append part of s to itself
80
template<typename... Ts>
81
void strAppend(std::string& result, Ts&& ...ts);
82
83
84
// Format an integer as a fixed-width hexadecimal value and insert it into
85
// a strCat() or strAppend() sequence.
86
// - If the value is small, leading zeros are printed.
87
// - If the value is too big, it gets truncated. Only the rightmost characters
88
//   are kept.
89
//
90
// For example:
91
//    s = strCat("The value is: 0x", hex_string<4>(value), '.');
92
////template<size_t N, typename T>
93
////strCatImpl::ConcatFixedWidthHexIntegral<N, T> hex_string(T t);
94
95
96
// Insert 'n' spaces into a strCat() or strAppend() sequence.
97
//
98
// For example:
99
//    s = strCat("The result is ", spaces(30 - item.size()), item);
100
//
101
// This can be more efficient than creating a temporary string containing 'n'
102
// spaces, like this
103
//    s = strCat("The result is ", std::string(30 - item.size(), ' '), item);
104
////strCatImpl::ConcatSpaces spaces(size_t t);
105
106
struct Digits {
107
	size_t n;
108
};
109
enum class HexCase : uint8_t {
110
	lower, upper
111
};
112
113
// --- Implementation details ---
114
115
namespace strCatImpl {
116
117
// ConcatUnit
118
// These implement various mechanisms to concatenate an object to a string.
119
// All these classes implement:
120
//
121
// - size_t size() const;
122
//     Returns the (exact) size in characters of the formatted object.
123
//
124
// - char* copy(char* dst) const;
125
//     Copy the formatted object to 'dst', returns an updated pointer.
126
template<typename T> struct ConcatUnit;
127
128
129
// Helper for types which are formatted via a temporary string object
130
struct ConcatViaString
131
{
132
	using NonTrivialToConstruct = void;
133
134
	explicit ConcatViaString(std::string s_)
135
		: s(std::move(s_))
136
	{
137
	}
138
139
	[[nodiscard]] size_t size() const
140
	{
141
		return s.size();
142
	}
143
144
	[[nodiscard]] char* copy(char* dst) const
145
	{
146
		std::ranges::copy(s, dst);
147
		return dst + s.size();
148
	}
149
150
private:
151
	std::string s;
152
};
153
154
// Helper for types which are printed via std::to_string(),
155
// e.g. floating point types.
156
template<typename T>
157
struct ConcatToString : ConcatViaString
158
{
159
	explicit ConcatToString(T t)
160
		: ConcatViaString(std::to_string(t))
161
	{
162
	}
163
};
164
165
166
// ConcatUnit<string_view>:
167
//   store the string view (copies the view, not the string)
168
template<> struct ConcatUnit<std::string_view>
169
{
170
	explicit 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
		std::ranges::copy(v, dst);
183
		return dst + v.size();
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
	explicit 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
	explicit 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
//  (without compiler warning on 't < 0' and '-t' when t is unsigned)
259
template<std::unsigned_integral T>
260
[[nodiscard]] FastUnsigned<T> absHelper(T t) { return t; }
261
262
template<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.
274
template<std::integral T> struct ConcatIntegral
275
{
276
	using NonTrivialToConstruct = void;
277
278
	static constexpr bool IS_SIGNED = std::numeric_limits<T>::is_signed;
279
	static constexpr size_t BUF_SIZE = 1 + std::numeric_limits<T>::digits10 + IS_SIGNED;
280
281
	explicit ConcatIntegral(T t)
282
	{
283
		auto p = this->end();
284
		std::unsigned_integral auto a = absHelper(t);
285
286
		do {
287
			*--p = static_cast<char>('0' + (a % 10));
288
			a /= 10;
289
		} while (a);
290
291
		if constexpr (IS_SIGNED) {
292
			if (t < 0) *--p = '-';
293
		}
294
		this->sz = static_cast<unsigned char>(this->end() - p);
295
	}
296
297
	[[nodiscard]] size_t size() const
298
	{
299
		return sz;
300
	}
301
302
	[[nodiscard]] char* copy(char* dst) const
303
	{
304
		std::ranges::copy(std::span{data(), sz}, dst);
305
		return dst + sz;
306
	}
307
308
	[[nodiscard]] char* copyTail(char* dst, size_t n) const
309
	{
310
		assert(n <= sz);
311
		std::ranges::copy(std::span{buf.data() + BUF_SIZE - n, n}, dst);
312
		return dst + n;
313
	}
314
315
	[[nodiscard]] operator std::string() const
316
	{
317
		return std::string(data(), this->size());
318
	}
319
320
private:
321
	[[nodiscard]] const char* data() const { return buf.data() + BUF_SIZE - sz; }
322
	[[nodiscard]] auto end() { return buf.end(); }
323
324
private:
325
	std::array<char, BUF_SIZE> buf;
326
	unsigned char sz;
327
};
328
329
330
// Format an integral as a hexadecimal value with a fixed number of characters.
331
// This fixed width means it either adds leading zeros or truncates the result
332
// (it keeps the rightmost digits).
333
template<HexCase Case, std::integral T> struct ConcatVariableWidthHexIntegral
334
{
335
	ConcatVariableWidthHexIntegral(Digits n_, T t_)
336
		: n(n_.n), t(t_)
337
	{
338
	}
339
340
	[[nodiscard]] size_t size() const
341
	{
342
		return n;
343
	}
344
345
	[[nodiscard]] char* copy(char* dst) const
346
	{
347
		char* p = dst + n;
348
		auto u = static_cast<FastUnsigned<T>>(t);
349
350
		static constexpr char A = (Case == HexCase::lower) ? 'a' : 'A';
351
		repeat(n, [&] {
352
			auto d = u & 15;
353
			*--p = (d < 10) ? static_cast<char>(d + '0')
354
			                : static_cast<char>(d - 10 + A);
355
			u >>= 4;
356
		});
357
358
		return dst + n;
359
	}
360
361
private:
362
	size_t n;
363
	T t;
364
};
365
366
// Format an integral as a hexadecimal value with a fixed number of characters.
367
// This fixed width means it either adds leading zeros or truncates the result
368
// (it keeps the rightmost digits).
369
template<size_t N, HexCase Case, std::integral T> struct ConcatFixedWidthHexIntegral
370
{
371
	explicit ConcatFixedWidthHexIntegral(T t_)
372
		: t(t_)
373
	{
374
	}
375
376
	[[nodiscard]] size_t size() const
377
	{
378
		return N;
379
	}
380
381
	char* copy(char* dst) const
382
	{
383
		char* p = dst + N;
384
		auto u = static_cast<FastUnsigned<T>>(t);
385
386
		static constexpr char A = (Case == HexCase::lower) ? 'a' : 'A';
387
		repeat(N, [&] {
388
			auto d = u & 15;
389
			*--p = (d < 10) ? static_cast<char>(d + '0')
390
			                : static_cast<char>(d - 10 + A);
391
			u >>= 4;
392
		});
393
394
		return dst + N;
395
	}
396
397
	friend std::ostream& operator<<(std::ostream& os, const ConcatFixedWidthHexIntegral& v)
398
	{
399
		std::array<char, N> buf;
400
		v.copy(buf.data());
401
		return os.write(buf.data(), N);
402
	}
403
404
private:
405
	T t;
406
};
407
408
// Format an integral as a hexadecimal value (variable width)
409
template<HexCase Case, std::integral T> struct ConcatHexIntegral
410
{
411
	explicit ConcatHexIntegral(T t)
412
	{
413
		using U = std::make_unsigned_t<T>;
414
		U u = static_cast<U>(t);
415
416
		char* p = buf.data() + buf.size();
417
		if (u == 0) {
418
			*--p = '0';
419
		} else {
420
			static constexpr char A = (Case == HexCase::lower) ? 'a' : 'A';
421
			do {
422
				auto d = u & 15;
423
				*--p = (d < 10) ? static_cast<char>(d + '0')
424
						: static_cast<char>(d - 10 + A);
425
				u >>= 4;
426
			} while (u);
427
		}
428
429
		n = static_cast<uint8_t>(buf.data() + buf.size() - p);
430
	}
431
432
	[[nodiscard]] size_t size() const { return n; }
433
434
	[[nodiscard]] char* copy(char* dst) const
435
	{
436
		const char* start = buf.data() + buf.size() - n;
437
		std::memcpy(dst, start, n);
438
		return dst + n;
439
	}
440
441
	friend std::ostream& operator<<(std::ostream& os, const ConcatHexIntegral& v)
442
	{
443
		const char* start = v.buf.data() + v.buf.size() - v.n;
444
		return os.write(start, v.n);
445
	}
446
private:
447
	std::array<char, sizeof(T) * 2> buf{};
448
	uint8_t n = 0;
449
};
450
451
// Format an integral as a binary value with a fixed number of characters.
452
// This fixed width means it either adds leading zeros or truncates the result
453
// (it keeps the rightmost digits).
454
template<size_t N, std::integral T> struct ConcatFixedWidthBinIntegral
455
{
456
	explicit ConcatFixedWidthBinIntegral(T t_)
457
		: t(t_)
458
	{
459
	}
460
461
	[[nodiscard]] size_t size() const
462
	{
463
		return N;
464
	}
465
466
	char* copy(char* dst) const
467
	{
468
		char* p = dst + N;
469
		auto u = static_cast<FastUnsigned<T>>(t);
470
471
		repeat(N, [&] {
472
			*--p = static_cast<char>((u & 1) + '0');
473
			u >>= 1;
474
		});
475
476
		return dst + N;
477
	}
478
479
	friend std::ostream& operator<<(std::ostream& os, const ConcatFixedWidthBinIntegral& v)
480
	{
481
		std::array<char, N> buf;
482
		v.copy(buf.data());
483
		return os.write(buf.data(), N);
484
	}
485
486
private:
487
	T t;
488
};
489
490
// Format an integral as a binary value (variable width).
491
template<std::integral T> struct ConcatBinIntegral
492
{
493
	explicit ConcatBinIntegral(T t) {
494
		using U = std::make_unsigned_t<T>;
495
		U u = static_cast<U>(t);
496
497
		char* p = buf.data() + buf.size();
498
		if (u == 0) {
499
			*--p = '0';
500
		} else {
501
			do {
502
				*--p = static_cast<char>((u & 1) + '0');
503
				u >>= 1;
504
			} while (u);
505
		}
506
507
		n = static_cast<uint8_t>(buf.data() + buf.size() - p);
508
	}
509
510
	[[nodiscard]] size_t size() const { return n; }
511
512
	[[nodiscard]] char* copy(char* dst) const
513
	{
514
		const char* start = buf.data() + buf.size() - n;
515
		std::memcpy(dst, start, n);
516
		return dst + n;
517
	}
518
519
	friend std::ostream& operator<<(std::ostream& os, const ConcatBinIntegral& v)
520
	{
521
		const char* start = v.buf.data() + v.buf.size() - v.n;
522
		return os.write(start, v.n);
523
	}
524
525
private:
526
	std::array<char, sizeof(T) * 8> buf{};
527
	uint8_t n = 0;
528
};
529
530
// Prints a number of spaces (without constructing a temporary string).
531
struct ConcatSpaces
532
{
533
	explicit ConcatSpaces(size_t n_)
534
		: n(n_)
535
	{
536
	}
537
538
	[[nodiscard]] size_t size() const
539
	{
540
		return n;
541
	}
542
543
	[[nodiscard]] char* copy(char* dst) const
544
	{
545
		std::ranges::fill(std::span{dst, n}, ' ');
546
		return dst + n;
547
	}
548
549
private:
550
	size_t n;
551
};
552
553
554
// Format an integral as a decimal value with a fixed number of characters.
555
// This fixed width means it either adds leading spaces or truncates the result
556
// (it keeps the rightmost digits).
557
template<size_t N, std::integral T> struct ConcatFixedWidthDecIntegral
558
{
559
	explicit ConcatFixedWidthDecIntegral(T t)
560
		: helper(t)
561
	{
562
	}
563
564
	[[nodiscard]] size_t size() const
565
	{
566
		return N;
567
	}
568
569
	[[nodiscard]] char* copy(char* dst) const
570
	{
571
		auto n2 = helper.size();
572
		if (N <= n2) {
573
			return helper.copyTail(dst, N);
574
		} else {
575
			ConcatSpaces spaces(N - n2);
576
			auto* p = spaces.copy(dst);
577
			return helper.copy(p);
578
		}
579
	}
580
581
private:
582
	ConcatIntegral<T> helper;
583
};
584
585
586
// Create a 'ConcatUnit<T>' wrapper object for a given 'T' value.
587
588
// Generic version: use the corresponding ConcatUnit<T> class. This can be
589
// a specialized version for 'T', or the generic (slow) version which uses
590
// operator<<(ostream&, T).
591
template<typename T>
592
[[nodiscard]] inline auto makeConcatUnit(const T& t)
593
{
594
	return ConcatUnit<T>(t);
595
}
596
597
// Overloads for various cases (strings, integers, floats, ...).
598
[[nodiscard]] inline auto makeConcatUnit(const std::string& s)
599
{
600
	return ConcatUnit<std::string_view>(s);
601
}
602
603
[[nodiscard]] inline auto makeConcatUnit(const char* s)
604
{
605
	return ConcatUnit<std::string_view>(s);
606
}
607
608
[[nodiscard]] inline auto makeConcatUnit(char* s)
609
{
610
	return ConcatUnit<std::string_view>(s);
611
}
612
[[nodiscard]] inline auto makeConcatUnit(const TemporaryString& s)
613
{
614
	return ConcatUnit<std::string_view>(s);
615
}
616
[[nodiscard]] inline auto makeConcatUnit(zstring_view s)
617
{
618
	return ConcatUnit<std::string_view>(s);
619
}
620
621
// Note: no ConcatIntegral<char> because that is printed as a single character
622
[[nodiscard]] inline auto makeConcatUnit(signed char c)
623
{
624
	return ConcatIntegral<signed char>(c);
625
}
626
627
[[nodiscard]] inline auto makeConcatUnit(unsigned char c)
628
{
629
	return ConcatIntegral<unsigned char>(c);
630
}
631
632
[[nodiscard]] inline auto makeConcatUnit(short s)
633
{
634
	return ConcatIntegral<short>(s);
635
}
636
637
[[nodiscard]] inline auto makeConcatUnit(unsigned short s)
638
{
639
	return ConcatIntegral<unsigned short>(s);
640
}
641
642
[[nodiscard]] inline auto makeConcatUnit(int i)
643
{
644
	return ConcatIntegral<int>(i);
645
}
646
647
[[nodiscard]] inline auto makeConcatUnit(unsigned u)
648
{
649
	return ConcatIntegral<unsigned>(u);
650
}
651
652
[[nodiscard]] inline auto makeConcatUnit(long l)
653
{
654
	return ConcatIntegral<long>(l);
655
}
656
657
[[nodiscard]] inline auto makeConcatUnit(unsigned long l)
658
{
659
	return ConcatIntegral<unsigned long>(l);
660
}
661
662
[[nodiscard]] inline auto makeConcatUnit(long long l)
663
{
664
	return ConcatIntegral<long long>(l);
665
}
666
667
[[nodiscard]] inline auto makeConcatUnit(unsigned long long l)
668
{
669
	return ConcatIntegral<unsigned long long>(l);
670
}
671
672
[[nodiscard]] inline auto makeConcatUnit(float f)
673
{
674
	return ConcatToString<float>(f);
675
}
676
677
[[nodiscard]] inline auto makeConcatUnit(double d)
678
{
679
	return ConcatToString<double>(d);
680
}
681
682
[[nodiscard]] inline auto makeConcatUnit(long double d)
683
{
684
	return ConcatToString<long double>(d);
685
}
686
687
template<HexCase Case, std::integral T>
688
[[nodiscard]] inline auto makeConcatUnit(const ConcatVariableWidthHexIntegral<Case, T>& t)
689
{
690
	return t;
691
}
692
693
template<size_t N, HexCase Case, std::integral T>
694
[[nodiscard]] inline auto makeConcatUnit(const ConcatFixedWidthHexIntegral<N, Case, T>& t)
695
{
696
	return t;
697
}
698
699
template<size_t N, std::integral T>
700
[[nodiscard]] inline auto makeConcatUnit(const ConcatFixedWidthBinIntegral<N, T>& t)
701
{
702
	return t;
703
}
704
705
template<std::integral T>
706
[[nodiscard]] inline auto makeConcatUnit(const ConcatBinIntegral<T>& t)
707
{
708
	return t;
709
}
710
711
template<size_t N, std::integral T>
712
[[nodiscard]] inline auto makeConcatUnit(const ConcatFixedWidthDecIntegral<N, T>& t)
713
{
714
	return t;
715
}
716
717
[[nodiscard]] inline auto makeConcatUnit(const ConcatSpaces& t)
718
{
719
	return t;
720
}
721
722
struct Empty
723
{
724
	[[nodiscard]] size_t size() const { return 0; }
725
	[[nodiscard]] char* copy(char* dst) const { return dst; }
726
};
727
728
template<typename... Ts>
729
struct Group
730
{
731
	std::tuple<Ts...> t; // expected: concat-unit like objects
732
733
	[[nodiscard]] size_t size() const
734
	{
735
		return std::apply([](const auto& ...x) { return (0 + ... + x.size()); }, t);
736
	}
737
738
	[[nodiscard]] char* copy(char* dst) const
739
	{
740
		std::apply([&](const auto& ...x) { ((dst = x.copy(dst)), ...); }, t);
741
		return dst;
742
	}
743
};
744
745
template<typename T>
746
using ConcatUnitForArg = decltype(makeConcatUnit(std::declval<T>()));
747
748
// Wrapper for callables whose evaluation must be deferred (e.g. in strCat_if when
749
// the condition may be false). The callable is invoked only when size()/copy() is
750
// actually needed.
751
template<typename F>
752
struct LazyStrCat
753
{
754
	explicit LazyStrCat(F f_)
755
		: f(std::move(f_)) {}
756
757
	F f;
758
};
759
760
template<typename F>
761
LazyStrCat(F&&) -> LazyStrCat<std::remove_cvref_t<F>>;
762
763
// ConcatUnit that invokes the callable on first use. Use with strCat_if() to
764
// avoid evaluating expressions when the condition is false (e.g. to prevent
765
// nullptr dereference or to skip expensive computation).
766
template<typename F>
767
struct LazyStrCatConcatUnit
768
{
769
	using R = std::invoke_result_t<F>;
770
	explicit LazyStrCatConcatUnit(F f_)
771
		: f(std::move(f_)) {}
772
773
	[[nodiscard]] size_t size() const
774
	{
775
		assert(!concatUnit); // strCat framework calls size() exactly once before copy()
776
		result.emplace(std::invoke(f));
777
		concatUnit.emplace(makeConcatUnit(*result));
778
		return concatUnit->size();
779
	}
780
781
	[[nodiscard]] char* copy(char* dst) const
782
	{
783
		assert(concatUnit); // size() must have been called first
784
		return concatUnit->copy(dst);
785
	}
786
787
private:
788
	mutable F f;
789
	mutable std::optional<R> result;
790
	mutable std::optional<ConcatUnitForArg<R>> concatUnit;
791
};
792
793
template<typename F>
794
[[nodiscard]] inline auto makeConcatUnit(const LazyStrCat<F>& t)
795
{
796
	return LazyStrCatConcatUnit<F>(t.f);
797
}
798
799
// For expensive types: lazily build ConcatUnit on first size()/copy().
800
template<typename T>
801
struct LazyConcatUnit
802
{
803
	explicit LazyConcatUnit(T t)
804
		: arg(std::move(t)) {}
805
806
	[[nodiscard]] size_t size() const
807
	{
808
		assert(!concatUnit); // strCat framework calls size() exactly once before copy()
809
		concatUnit.emplace(makeConcatUnit(std::move(arg)));
810
		return concatUnit->size();
811
	}
812
813
	[[nodiscard]] char* copy(char* dst) const
814
	{
815
		assert(concatUnit); // size() must have been called first
816
		return concatUnit->copy(dst);
817
	}
818
819
private:
820
	mutable T arg;
821
	mutable std::optional<ConcatUnitForArg<T>> concatUnit;
822
};
823
824
// ConcatUnit types can opt into lazy construction by defining NonTrivialToConstruct.
825
template<typename T, typename = void>
826
struct IsNonTrivialToConstruct : std::false_type {};
827
template<typename T>
828
struct IsNonTrivialToConstruct<T, std::void_t<typename T::NonTrivialToConstruct>> : std::true_type {};
829
830
template<typename T>
831
[[nodiscard]] auto makeLazyConcatUnit(T&& t)
832
{
833
	using T2 = std::remove_cvref_t<T>;
834
	if constexpr (IsNonTrivialToConstruct<ConcatUnitForArg<T2>>::value) {
835
		return LazyConcatUnit<T2>(std::forward<T>(t));
836
	} else {
837
		return makeConcatUnit(std::forward<T>(t));
838
	}
839
}
840
841
// Per-element lazy: cheap types -> ConcatUnit immediately, expensive -> LazyConcatUnit.
842
template<typename... Ts>
843
[[nodiscard]] auto makeLazyConcatUnits(Ts&&... ts)
844
{
845
	if constexpr (sizeof...(Ts) == 1) { // single element doesn't need Group
846
		return makeLazyConcatUnit(std::forward<Ts>(ts)...);
847
	} else {
848
		return Group{std::tuple{makeLazyConcatUnit(std::forward<Ts>(ts))...}};
849
	}
850
}
851
852
template<typename IfPart, typename ElsePart>
853
struct Conditional
854
{
855
	bool condition;
856
	IfPart ifPart;
857
	ElsePart elsePart;
858
859
	[[nodiscard]] size_t size() const
860
	{
861
		return condition ? ifPart.size() : elsePart.size();
862
	}
863
864
	[[nodiscard]] char* copy(char* dst) const
865
	{
866
		return condition ? ifPart.copy(dst) : elsePart.copy(dst);
867
	}
868
869
	template<typename BOOL, typename... Ts> [[nodiscard]] auto else_if(BOOL c, Ts&&... ts) &&;
870
	template<typename... Ts> [[nodiscard]] auto else_(Ts&&... ts) &&;
871
};
872
873
template<typename T> struct IsConditional : std::false_type {};
874
template<typename IfPart, typename ElsePart>
875
struct IsConditional<Conditional<IfPart, ElsePart>> : std::true_type {};
876
877
template<auto> static inline constexpr bool always_false = false;
878
879
template<typename IfPart, typename ElsePart, typename NewPart>
880
[[nodiscard]] auto replaceEmptyTail(Conditional<IfPart, ElsePart>&& c, NewPart&& part)
881
{
882
	if constexpr (std::same_as<ElsePart, Empty>) {
883
		return Conditional{
884
			c.condition, std::move(c.ifPart), std::forward<NewPart>(part)
885
		};
886
	} else if constexpr (IsConditional<ElsePart>::value) {
887
		return Conditional{
888
			c.condition,
889
			std::move(c.ifPart),
890
			replaceEmptyTail(std::move(c.elsePart), std::forward<NewPart>(part))
891
		};
892
	} else {
893
		static_assert(always_false<sizeof(ElsePart)>,
894
			"Cannot chain .else_() or .else_if() after .else_()");
895
		std::unreachable();
896
	}
897
}
898
899
template<typename BOOL, typename... Ts>
900
[[nodiscard]] inline auto if_(BOOL condition, Ts&&... ts)
901
{
902
	return Conditional{static_cast<bool>(condition), makeLazyConcatUnits(std::forward<Ts>(ts)...), Empty{}};
903
}
904
905
template<typename IfPart, typename ElsePart>
906
template<typename BOOL, typename... Ts>
907
[[nodiscard]] auto Conditional<IfPart, ElsePart>::else_if(BOOL c, Ts&&... ts) &&
908
{
909
	return replaceEmptyTail(std::move(*this), if_(c, std::forward<Ts>(ts)...));
910
}
911
912
template<typename IfPart, typename ElsePart>
913
template<typename... Ts>
914
[[nodiscard]] auto Conditional<IfPart, ElsePart>::else_(Ts&&... ts) &&
915
{
916
	return replaceEmptyTail(std::move(*this),
917
		makeLazyConcatUnits(std::forward<Ts>(ts)...));
918
}
919
920
template<typename IfPart, typename ElsePart>
921
[[nodiscard]] inline auto makeConcatUnit(const Conditional<IfPart, ElsePart>& t)
922
{
923
	return t;
924
}
925
926
927
// Calculate the total size for a bunch (a tuple) of ConcatUnit<T> objects.
928
// That is, calculate the sum of calling the size() method on each ConcatUnit.
929
template<typename Tuple, size_t... Is>
930
[[nodiscard]] size_t calcTotalSizeHelper(const Tuple& t, std::index_sequence<Is...>)
931
{
932
	return (... + std::get<Is>(t).size());
933
}
934
935
template<typename... Ts>
936
[[nodiscard]] size_t calcTotalSize(const std::tuple<Ts...>& t)
937
{
938
	return calcTotalSizeHelper(t, std::index_sequence_for<Ts...>{});
939
}
940
941
942
// Copy each ConcatUnit<T> in the given tuple to the final result.
943
template<typename Tuple, size_t... Is>
944
void copyUnitsHelper(char* dst, const Tuple& t, std::index_sequence<Is...>)
945
{
946
	auto l = { ((dst = std::get<Is>(t).copy(dst)) , 0)... };
947
	(void)l;
948
}
949
950
template<typename... Ts>
951
void copyUnits(char* dst, const std::tuple<Ts...>& t)
952
{
953
	copyUnitsHelper(dst, t, std::index_sequence_for<Ts...>{});
954
}
955
956
// Fast integral -> string conversion. (Standalone version, result is not part
957
// of a larger string).
958
[[nodiscard]] inline std::string to_string(std::integral auto x)
959
{
960
	return ConcatIntegral(x);
961
}
962
963
} // namespace strCatImpl
964
965
966
// Generic version
967
template<typename... Ts>
968
[[nodiscard]] std::string strCat(Ts&& ...ts)
969
{
970
	// Strategy:
971
	// - For each parameter (of any type) we create a ConcatUnit object.
972
	// - We sum the results of callings size() on all those objects.
973
	// - We allocate a string of that total size.
974
	// - We copy() each ConcatUnit into that string.
975
976
	auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
977
	auto size = strCatImpl::calcTotalSize(t);
978
979
	std::string result;
980
	result.resize_and_overwrite(size, [&](char* dst, size_t /*sz*/) {
981
		//assert(sz == size) <-- not true with gcc-12 (bug)
982
		// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104222
983
		strCatImpl::copyUnits(dst, t);
984
		return size;
985
	});
986
	return result;
987
}
988
989
// Optimized strCat() for the case that the 1st parameter is a temporary
990
// (rvalue) string. In that case we can append to that temporary.
991
template<typename... Ts>
992
[[nodiscard]] std::string strCat(std::string&& first, Ts&& ...ts)
993
{
994
	strAppend(first, std::forward<Ts>(ts)...);
995
	return std::move(first);
996
}
997
998
// Degenerate case
999
[[nodiscard]] inline std::string strCat()
1000
{
1001
	return {};
1002
}
1003
1004
// Extra overloads. These don't change and/or extend the above functionality,
1005
// but in some cases they might improve performance a bit (see notes above
1006
// about uninitialized string resize). With these overloads strCat()/strAppend()
1007
// should never be less efficient than a sequence of + or += string-operations.
1008
[[nodiscard]] inline std::string strCat(const std::string& x) { return x; }
1009
[[nodiscard]] inline std::string strCat(std::string&&      x) { return std::move(x); }
1010
[[nodiscard]] inline std::string strCat(const char*        x) { return {x}; }
1011
[[nodiscard]] inline std::string strCat(char               x) { return std::string(1, x); } // Not: return {1, x};
1012
[[nodiscard]] inline std::string strCat(std::string_view   x) { return {x.data(), x.size()}; }
1013
1014
[[nodiscard]] inline std::string strCat(signed char        x) { return strCatImpl::to_string(x); }
1015
[[nodiscard]] inline std::string strCat(unsigned char      x) { return strCatImpl::to_string(x); }
1016
[[nodiscard]] inline std::string strCat(short              x) { return strCatImpl::to_string(x); }
1017
[[nodiscard]] inline std::string strCat(unsigned short     x) { return strCatImpl::to_string(x); }
1018
[[nodiscard]] inline std::string strCat(int                x) { return strCatImpl::to_string(x); }
1019
[[nodiscard]] inline std::string strCat(unsigned           x) { return strCatImpl::to_string(x); }
1020
[[nodiscard]] inline std::string strCat(long               x) { return strCatImpl::to_string(x); }
1021
[[nodiscard]] inline std::string strCat(unsigned long      x) { return strCatImpl::to_string(x); }
1022
[[nodiscard]] inline std::string strCat(long long          x) { return strCatImpl::to_string(x); }
1023
[[nodiscard]] inline std::string strCat(unsigned long long x) { return strCatImpl::to_string(x); }
1024
1025
[[nodiscard]] inline std::string strCat(const std::string& x, const std::string& y) { return x + y; }
1026
[[nodiscard]] inline std::string strCat(const char*        x, const std::string& y) { return x + y; }
1027
[[nodiscard]] inline std::string strCat(char               x, const std::string& y) { return x + y; }
1028
[[nodiscard]] inline std::string strCat(const std::string& x, const char*        y) { return x + y; }
1029
[[nodiscard]] inline std::string strCat(const std::string& x, char               y) { return x + y; }
1030
[[nodiscard]] inline std::string strCat(std::string&&      x, const std::string& y) { return std::move(x) + y; }
1031
[[nodiscard]] inline std::string strCat(const std::string& x, std::string&&      y) { return x + std::move(y); }
1032
[[nodiscard]] inline std::string strCat(std::string&&      x, std::string&&      y) { return std::move(x) + std::move(y); }
1033
[[nodiscard]] inline std::string strCat(const char*        x, std::string&&      y) { return x + std::move(y); }
1034
[[nodiscard]] inline std::string strCat(char               x, std::string&&      y) { return x + std::move(y); }
1035
[[nodiscard]] inline std::string strCat(std::string&&      x, const char*        y) { return std::move(x) + y; }
1036
[[nodiscard]] inline std::string strCat(std::string&&      x, char               y) { return std::move(x) + y; }
1037
1038
template<typename... Ts> [[nodiscard]] TemporaryString tmpStrCat(Ts&&... ts)
1039
{
1040
	auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
1041
	auto size = strCatImpl::calcTotalSize(t);
1042
	return TemporaryString(
1043
	        size, [&](char* dst) { strCatImpl::copyUnits(dst, t); });
1044
}
1045
1046
// Generic version
1047
template<typename... Ts>
1048
void strAppend(std::string& result, Ts&& ...ts)
1049
{
1050
	// Implementation strategy is similar to strCat(). Main difference is
1051
	// that we now extend an existing string instead of creating a new one.
1052
1053
	auto t = std::tuple(strCatImpl::makeConcatUnit(std::forward<Ts>(ts))...);
1054
	auto extraSize = strCatImpl::calcTotalSize(t);
1055
	auto oldSize = result.size();
1056
1057
	auto newSize = oldSize + extraSize;
1058
	result.resize_and_overwrite(newSize, [&](char* p, size_t /*sz*/) {
1059
		//assert(sz == newSize); // see above (gcc-12 bug)
1060
		// p[0..oldSize-1] still/already contains old content
1061
		strCatImpl::copyUnits(&p[oldSize], t);
1062
		return newSize;
1063
	});
1064
}
1065
1066
// Degenerate case
1067
inline void strAppend(std::string& /*x*/)
1068
{
1069
	// nothing
1070
}
1071
1072
// Extra overloads, see strCat().
1073
inline void strAppend(std::string& x, const std::string& y) { x += y; }
1074
inline void strAppend(std::string& x, const char*        y) { x += y; }
1075
inline void strAppend(std::string& x, std::string_view   y) { x.append(y.data(), y.size()); }
1076
1077
template<typename BOOL, typename... Ts>
1078
[[nodiscard]] inline auto strCat_if(BOOL condition, Ts&&... ts)
1079
{
1080
	return strCatImpl::if_(condition, std::forward<Ts>(ts)...);
1081
}
1082
1083
// Defer evaluation of an expression until it is actually needed. Use inside
1084
// strCat_if() or .else_if() to avoid evaluating when the condition is false
1085
// (e.g. to prevent nullptr dereference or skip expensive computation).
1086
// The callable must return something that strCat can concatenate.
1087
template<typename F>
1088
[[nodiscard]] inline auto strCat_lazy(F&& f)
1089
{
1090
	return strCatImpl::LazyStrCat(std::forward<F>(f));
1091
}
1092
#define STRCAT_LAZY(...) strCat_lazy([&]{ return (__VA_ARGS__); })
1093
1094
template<HexCase Case = HexCase::lower, std::integral T>
1095
[[nodiscard]] inline strCatImpl::ConcatVariableWidthHexIntegral<Case, T> hex_string(Digits n, T t)
1096
{
1097
	return {n, t};
1098
}
1099
1100
template<size_t N, HexCase Case = HexCase::lower, std::integral T>
1101
[[nodiscard]] inline auto hex_string(T t)
1102
{
1103
	return strCatImpl::ConcatFixedWidthHexIntegral<N, Case, T>{t};
1104
}
1105
1106
template<HexCase Case = HexCase::lower, std::integral T>
1107
[[nodiscard]] inline auto hex_string(T t)
1108
{
1109
	return strCatImpl::ConcatHexIntegral<Case, T>{t};
1110
}
1111
1112
template<size_t N, std::integral T>
1113
[[nodiscard]] inline auto bin_string(T t)
1114
{
1115
	return strCatImpl::ConcatFixedWidthBinIntegral<N, T>{t};
1116
}
1117
1118
template<std::integral T>
1119
[[nodiscard]] inline auto bin_string(T t)
1120
{
1121
	return strCatImpl::ConcatBinIntegral<T>{t};
1122
}
1123
1124
template<size_t N, std::integral T>
1125
[[nodiscard]] inline auto dec_string(T t)
1126
{
1127
	return strCatImpl::ConcatFixedWidthDecIntegral<N, T>{t};
1128
}
1129
1130
[[nodiscard]] inline auto spaces(size_t n)
1131
{
1132
	return strCatImpl::ConcatSpaces{n};
1133
}
1134
1135
#endif