-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathTypeTraits.h
8434 lines (8089 loc) · 526 KB
/
TypeTraits.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#ifndef TYPETRAITS
#define TYPETRAITS
/////////////////////////////////////////////////////////////////////////////
// LICENSE NOTICE
// --------------
// Copyright (c) Hexadigm Systems
//
// Permission to use this software is granted under the following license:
// https://www.hexadigm.com/GenericLib/License.html
//
// This copyright notice must be included in this and all copies of the
// software as described in the above license.
//
// DESCRIPTION
// -----------
// Header file implementing "FunctionTraits" (fully) described here
// https://github.com/HexadigmSystems/FunctionTraits. More generally
// however, the header contains miscellaneous type traits similar to those
// in the native C++ header <type_traits>. Its main focus for now however is
// "FunctionTraits". All other "public" declarations are used to support it
// (read on for what "public" means), and while they can also be used by end
// users, they're not documented at the above link (but are largely
// documented in this header). Only declarations directly related to
// "FunctionTraits" are (fully) documented at the above link.
//
// Note that this header is supported by GCC, Microsoft, Clang and Intel
// compilers only at this writing (C++17 and later - the check for
// CPP17_OR_LATER just below causes all code to be preprocessed out
// otherwise). A separate check below also ensures the minimum supported
// versions of these compilers are running or a #error terminates
// compilation. Note that this file depends on (#includes)
// "CompilerVersions.h" as well. All other dependencies are native C++
// headers with the exception of the native Windows header "tchar.h" on MSFT
// platforms (or any other supported compiler running in Microsoft VC++
// compatibility mode). "tchar.h" must be in the #include search path (and
// normally will be when targeting MSFT).
//
// Lastly, note that all declarations in this file are in namespace
// "StdExt". Everything is available for public use except declarations in
// (nested) namespace "StdExt::Private" (reserved for internal use). Macros
// however don't respect namespaces but all macros #defined for internal use
// only are documented as such. All others are available for public use (if
// not explicitly documented for internal use).
/////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
// Our header containing mostly #defined C++ version constants
// indicating which version of C++ is in effect (which we can
// test for below as required - see "C++ Version Macros" in the
// following file). A few other compiler-related declarations
// also exist however. Note that we #include this first so we
// can immediately start using these version constants (in
// particular CPP17_OR_LATER seen just below - we don't support
// earlier versions)
////////////////////////////////////////////////////////////////
#include "CompilerVersions.h"
//////////////////////////////////////////////////////////////
// This header supports C++17 and later only. All code below
// ignored otherwise (preprocessed out).
//////////////////////////////////////////////////////////////
#if CPP17_OR_LATER
//////////////////////////////////////////////////////////////
// GCC compiler running? (the real one or any one compatible
// with it but *not* any compiler we explicitly support which
// includes Clang and Intel only at this writing - we
// independently test for them below but other GCC compatible
// compilers that we don't explicitly support will be handled
// by the following call instead, since they claim to be GCC
// compatible) ...
//////////////////////////////////////////////////////////////
#if defined(GCC_COMPILER)
///////////////////////////////////////////////////
// We only support GCC >= 10.2. Abort compilation
// otherwise.
///////////////////////////////////////////////////
#if __GNUC__ <= 10 && \
(__GNUC__ != 10 || __GNUC_MINOR__ < 2)
#error "Unsupported version of GCC detected. Must be >= V10.2 (this header doesn't support earlier versions)"
/////////////////////////////////////////////////////////////
// #including this non-existent file forces compilation to
// immediately stop. Otherwise more errors will occur before
// compilation finally stops. Just a hack and not really
// guaranteed to work but testing shows it normally does
// (for GCC and all other compilers we support at this
// writing). Other alternative techniques such as #pragmas
// to stop compilation on the first error aren't guaranteed
// to be reliable either. They (generally) don't affect the
// preprocessor for instance so compilation doesn't stop
// when the #error statement above is encountered. The
// #error message is displayed but compilation resumes,
// resulting in additional (extraneous) error messages
// before compilation finally stops. The upshot is that we'll
// just rely on the following for now to prevent these
// additional errors from being displayed after the #error
// message above (since the extra error messages are just
// a distraction from the actual #error message above).
/////////////////////////////////////////////////////////////
#include <StopCompilingNow> // Hack to force compilation to immediately stop.
// See comments above for details.
#endif
///////////////////////////////////////////////////////////////////
// Microsoft compiler (VC++) running? (the real VC++, not another
// compiler running in Microsoft VC++ compatibility mode)
///////////////////////////////////////////////////////////////////
#elif defined(MICROSOFT_COMPILER)
///////////////////////////////////////////////////////////
// Are we now running VC++ from a VS2017 release (opposed
// to VS2019 or later). Can't be from VS2015 or earlier at
// this point since CPP17_OR_LATER is currently #defined
// (we checked for it earlier), so we must be targeting
// VS2017 or later (since C++17 only became available in
// VS2017). If the following is true then the version of
// VC++ now running must therefore be from VS2017 (no
// earlier version of VS possible at this point).
///////////////////////////////////////////////////////////
#if _MSC_VER < MSC_VER_2019
//////////////////////////////////////////////////////////
// We only support VC++ >= 19.16 which was released with
// Visual Studio 2017 version 15.9. Abort compilation
// otherwise.
//////////////////////////////////////////////////////////
#if _MSC_VER >= MSC_VER_2017_V15_9 // VC++ 19.16
////////////////////////////////////////////////////////
// For internal use only (we #undef it later). Compiler
// is a VS2017 release of MSVC.
////////////////////////////////////////////////////////
#define MSVC_FROM_VISUAL_STUDIO_2017
#else
////////////////////////////////////////////////////
// Note that VC++ normally stops compiling as soon
// as this is encountered, so we don't rely on the
// #include <StopCompilingNow> hack used for the
// other compilers (above and below). See:
//
// #error directive (C/C++)
// https://learn.microsoft.com/en-us/cpp/preprocessor/hash-error-directive-c-cpp?view=msvc-170
////////////////////////////////////////////////////
#error "Unsupported version of Microsoft Visual C++ detected. Must be >= V19.16 (normally released with VS2017 V15.9). This header doesn't support earlier versions."
#endif
#endif
// Clang compiler running? (possibly in Microsoft VC++ compatibility mode - ok)
#elif defined(CLANG_COMPILER)
/////////////////////////////////////////////////////
// We only support Clang >= 11.0. Abort compilation
// otherwise.
/////////////////////////////////////////////////////
#if __clang_major__ < 11
#error "Unsupported version of Clang detected. Must be >= V11.0 (this header doesn't support earlier versions)"
#include <StopCompilingNow> // Hack to force compilation to immediately stop.
// See comments for GCC compiler above for details.
#endif
// Intel compiler (DPC++/C++) running? (possibly in Microsoft VC++ compatibility mode - ok)
#elif defined(INTEL_COMPILER)
////////////////////////////////////////////////////////
// We only support Intel >= 2021.4.0. Abort compilation
// otherwise.
////////////////////////////////////////////////////////
#if __INTEL_LLVM_COMPILER < 20210400
#error "Unsupported version of Intel oneAPI DPC++/C++ detected. Must be >= V2021.4.0 (this header doesn't support earlier versions)"
#include <StopCompilingNow> // Hack to force compilation to immediately stop.
// See comments for GCC compiler above for details.
#endif
#else
#error "Unsupported compiler (GCC, Microsoft, Clang and Intel are the only ones supported at this writing)"
#endif
///////////////////////////////////////////////////
// Sanity check only. Should always be false (for
// internal use only - #defined below if required)
///////////////////////////////////////////////////
#if defined(DECLARE_MACROS_ONLY)
#error "DECLARE_MACROS_ONLY already #defined (for internal use only so never should be)"
#endif
/////////////////////////////////////////////////////////////////////////////
// If all 3 conditions we're testing here are true then this header is now
// being #included by a client in the module version of the header (the
// module named "TypeTraits" in "TypeTraits.cppm" or whatever the user may
// have renamed the latter file's extension to). In this case we know
// modules (a C++20 feature), are supported (the 1st condition tests
// CPP20_OR_LATER for this but will be replaced with the official C++
// feature constant "__cpp_modules" in a later release since it's not yet
// supported by all compilers), and the 2nd condition then checks if
// STDEXT_USE_MODULES was #defined by the user themself, indicating they've
// added the ".cppm" files to their project (in this case the
// "TypeTraits.cppm" module) and wish to use it. In that case we'll go on to
// declare only #defined (public) macros in this header instead of all other
// C++ declarations that are also normally declared (when STDEXT_USE_MODULES
// isn't #defined), since those C++ declarations will now originate from the
// module itself (which we import via "import TypeTraits" in the code just
// below, though the user might also do this themself). Therefore, when a
// user #includes this header in the module version, only the macros in the
// header will need to be declared since C++ modules don't export #defined
// macros (we #define an internal constant DECLARE_MACROS_ONLY below to
// facilitate this). If the user hasn't #defined STDEXT_USE_MODULES however
// then they wish to use this header normally (declare everything as usual),
// even if they've also added the module to their project as well (which
// will still be successfully compiled), though that would be very unlikely
// (why add the module if they're not going to use it? - if they've added it
// then they'll normally #define STDEXT_USE_MODULES as well). As for the 3rd
// condition we're testing here, STDEXT_BUILDING_MODULE_TYPETRAITS (an
// internal constant), this is #defined by the module itself
// (TypeTraits.cppm) before #including this header in its global fragment
// section. The module then simply exports all public declarations from this
// header in its purview section via "using" declarations.
// STDEXT_BUILDING_MODULE_TYPETRAITS is therefore #defined only if the
// module itself is now being built. If #defined then it must be the module
// itself now #including us and if not #defined then it must be a client of
// the module #including us instead (which is what the following tests for
// in its 3rd condition - if all 3 conditions are true then it must be a
// user #including us, not the module itself, so we only declare the
// #defined macros in this header instead of all other declarations, as
// described above).
/////////////////////////////////////////////////////////////////////////////
#if CPP20_OR_LATER && defined(STDEXT_USE_MODULES) && !defined(STDEXT_BUILDING_MODULE_TYPETRAITS) // See comments about CPP20_OR_LATER above (to be
// replaced by "__cpp_modules" in a future release)
// "import std" not currently in effect? (C++23 or later)
#if !defined(STDEXT_IMPORTED_STD)
///////////////////////////////////////////////////
// Always pick up <type_traits> even when clients
// are #including us in the module version of
// "TypeTraits" (i.e., when "TypeTraits.cppm" has
// been added to the project). Most clients that
// #include "TypeTraits.h" would expect
// <type_traits> to also be picked up so in the
// module version we still #include it here as a
// convenience to clients only. Note that when
// STDEXT_IMPORTED_STD is true however (we just
// tested it above and it's false), then
// "import std" is currently in effect so
// <type_traits> is already available.
///////////////////////////////////////////////////
#include <type_traits>
#endif
//////////////////////////////////////////////////////
// Importing the "TypeTraits" module as a convenience
// to module clients. This way clients can simply
// #include this header without having to explicitly
// import the "TypeTraits" module themselves (since
// this header does it for them). It's harmless
// though if they've already imported the
// "TypeTraits" module on their own (which is more
// natural anyway - relying on this header may even
// confuse some users who might not realize that a
// "#include TypeTraits.h" statement is actually
// importing the "TypeTraits" module to pick up all
// public declarations in the header instead of
// declaring them in the header itself - this is how
// the header behaves when STDEXT_USE_MODULES is
// #defined). #including this header however will
// also pick up all public macros in the header since
// modules don't export macros (so if clients simply
// import the "TypeTraits" module and don't #include
// this header, they'll have access to all exported
// declarations in the module which originate from
// this header, but none of the macros in the header
// - fine if they don't use any of them though).
//////////////////////////////////////////////////////
import TypeTraits;
//////////////////////////////////////////////////////
// All declarations in this module are now available
// via the import statement just above. Only macros
// will be missing since modules don't export them
// (so the above import statement doesn't include
// them). The following will therefore ensure that
// only (public) macros will be #defined in the
// remaining code that follows. All other
// declarations are preprocessed out because they're
// already available via the import statement just
// above.
//////////////////////////////////////////////////////
#define DECLARE_MACROS_ONLY
//////////////////////////////////////////////////////////////
// We're now coming through either because the module version
// of this header isn't installed ("TypeTraits.cppm" or
// whatever the user may have renamed the extension to), so
// the header is handled normally (we declare everything
// below as usual), or it is installed but the latter file
// (module) is now #including us during its own build (it
// always does so the header is also treated normally when
// that occurs - again, everything is declared below as
// usual).
//////////////////////////////////////////////////////////////
#else
// "import std" not currently in effect? (C++23 or later)
#if !defined(STDEXT_IMPORTED_STD)
// Standard C/C++ headers
#include <array>
#include <cstddef>
#include <iostream>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#endif
// Everything below in this namespace
namespace StdExt
{
/////////////////////////////////////////////////////
// AlwaysFalse_v. Used in templates only normally
// where you need to always pass "false" for some
// purpose but in a way that's dependent on a
// template arg (instead of passing false directly).
// In almost all real-world cases however it will be
// used as the 1st arg to "static_assert", where
// the 1st arg must always be false. Can't use
// "false" directly or it will always trigger the
// "static_assert" (even if that code is never
// called or instantiated). Making it a (template)
// dependent name instead eliminates the problem
// (use the following IOW instead of "false"
// directly). See:
//
// // always_false<T>
// https://artificial-mind.net/blog/2020/10/03/always-false //
//
// How can I create a type-dependent expression that is always false?
// https://devblogs.microsoft.com/oldnewthing/20200311-00/?p=103553
/////////////////////////////////////////////////////
template <typename...>
inline constexpr bool AlwaysFalse_v = false;
////////////////////////////////////////////////////////
// AlwaysTrue_v. Similar to "AlwaysFalse_v" just above
// but used wherever you require "true" in a way that's
// dependent on a template arg. Very rarely used in
// practice however (not many uses for it), unlike
// "AlwaysFalse_v" just above which is more frequently
// used to trigger a "static_assert" where required.
// See "AlwaysFalse_v" above.
////////////////////////////////////////////////////////
template <typename...>
inline constexpr bool AlwaysTrue_v = true;
/////////////////////////////////////////////////////////////////////
// Private namespace (for internal use only) used to implement
// "TypeName_v" declared just after this namespace. Allows you to
// retrieve the type name for any type as a compile-time string
// (std::basic_string_view). See "TypeName_v" for full details (it
// just defers to "TypeNameImpl::Get()" in the following namespace).
/////////////////////////////////////////////////////////////////////
namespace Private
{
///////////////////////////////////////////////////////
// Identical to "tstring_view::operator==()" but this
// has a bug in the VS2017 release of VC++ (details
// don't matter since VS2017 is getting old now
// anyway). The bug was fixed in the VS2019 release of
// VC++. If (when) the VS2017 release of VC++ is
// eventually dropped, the function can be removed and
// calls to it can simply be replaced with a call to
// "str1 == str2" (i.e., the first return statement
// seen below).
///////////////////////////////////////////////////////
inline constexpr bool IsEqualTo(tstring_view str1,
tstring_view str2) noexcept
{
////////////////////////////////////////////////
// Any compiler other than VC++ from Microsoft
// Visual Studio 2017 ...
////////////////////////////////////////////////
#if !defined(MSVC_FROM_VISUAL_STUDIO_2017)
return (str1 == str2);
//////////////////////////////////////////////////
// VC++ from Visual Studio 2017. Code just above
// should work there as well but it's buggy
// (can return false even when true). The
// following is a manual work-around (equivalent
// to code above).
//////////////////////////////////////////////////
#else
if (str1.size() == str2.size())
{
for (tstring_view::size_type i = 0; i < str1.size(); ++i)
{
if (str1[i] != str2[i])
{
return false;
}
}
return true;
}
else
{
return false;
}
#endif
};
///////////////////////////////////////////////////
// Identical to "tstring_view::ends_with()" but
// this member isn't "constexpr" until C++20. We
// therefore just defer to the latter in C++20 or
// later or roll our own (equivalent) otherwise
// (in C++17 - earlier versions not possible at
// this point - check for CPP17_OR_LATER at top of
// file ensures this). Note that this function can
// be removed and simply replaced with calls to
// "str.ends_with()" if we ever drop support for
// C++17.
///////////////////////////////////////////////////
inline constexpr bool EndsWith(tstring_view str,
tstring_view suffix) noexcept
{
#if CPP20_OR_LATER
// Not available until C++20
return str.ends_with(suffix);
#else
///////////////////////////////////////////////////////
// Roll our own in C++17 (no earlier version possible
// at this stage - checked for CPP17_OR_LATER at top
// of file and preprocessed out any earlier versions).
///////////////////////////////////////////////////////
return str.size() >= suffix.size() &&
IsEqualTo(str.substr(str.size() - suffix.size()),
suffix);
#endif
}
////////////////////////////////////////////////////////////
// Converts "str" to a "std::array" consisting of all chars
// in "str" at the indexes specified by the 2nd arg (a
// collection of indexes in "str" indicating which chars in
// "str" will be copied into the array). A NULL terminator
// is also added at the end. The size of the returned
// "std::array" is therefore the number of indexes in the
// 2nd arg plus 1 for the NULL terminator. The array itself
// is populated with all chars in "str" at these particular
// indexes (again, with a NULL terminator added). The type
// of the returned array is therefore:
//
// std::array<tchar, sizeof...(I) + 1>
//
// Note that in practice this function is usually called to
// convert an entire constexpr string to a "std::array" so
// the function is usually called this way:
//
// // Any "constexpr" string
// constexpr tstring_view str = _T("Testing");
//
// ///////////////////////////////////////////////////////
// // Convert above to a "std::array". The 2nd arg is
// // just the sequential sequence {0, 1, 2, ..., N - 1}
// // where "N" is "str.size()", so all chars in "str"
// // are copied over (in the expected order 0 to N - 1).
// ///////////////////////////////////////////////////////
// constexpr auto strAsArray = StrToNullTerminatedArray(str, std::make_index_sequence<str.size()>());
//
// The 2nd arg above is therefore just the sequence of
// (std::size_t) integers in the range 0 to the number of
// characters in "str" - 1. The function therefore returns
// a "std::array" containing a copy of "str" itself since
// the 2nd arg specifies every index in "str" (so each
// character at these indexes is copied to the array). The
// returned type in the above example is therefore the
// following (7 characters in the above string plus 1 for
// the NULL terminator which we always manually add):
//
// std::array<tchar, 8>;
//
// Note that having to pass "std::make_index_sequence" as
// seen in the above example is syntactically ugly however.
// It's therefore cleaner to call the other
// "StrToNullTerminatedArray" overload just below instead,
// which is designed for this purpose (to copy all of "str"
// which most will usually be doing - see overload below
// for details). It simply defers to the overload you're
// now reading but it's a bit cleaner.
//
// Even the cleaner overload below is still syntactically
// ugly however (both overloads are), but unfortunately C++
// doesn't support "constexpr" parameters at this writing,
// which would eliminate the issue. The issue is that the
// 2nd template arg of "std::array" is the size of the
// array itself and this arg must be known at compile-time
// of course (since it's a template arg). Since the "str"
// parameter of both overloads isn't "constexpr" however
// (since C++ doesn't currently support "constexpr"
// parameters), neither function can directly pass
// "str.size()" as the 2nd template parameter to
// "std::array", even though "str" itself may be
// "constexpr" at the point of call. It means that even
// when "str" is "constexpr" at the point of call, the user
// is still forced to pass "str.size()" as a template
// parameter at the point of call since the function itself
// can't legally do it (it can't pass it as the 2nd
// template arg of "std::array" since "str" isn't
// "constexpr" inside the function - it must therefore be
// passed as a template parameter at the point of call
// instead, which makes the syntax of these functions
// unnatural). The situation is ugly and unwieldy since it
// would be much cleaner and more natural to just grab the
// size from "str.size()" inside the function itself, but
// the language doesn't support it in a "constexpr" context
// at this writing (maybe one day).
//
// The upshot is that the following overload is designed to
// circumvent this C++ deficiency but will rarely be called
// directly by most users (unless it's needed to copy a
// different sequence of characters from "str" to the
// returned array other than all of them). If you need to
// copy all of "str" to the returned array (usually the
// case), then it's cleaner to call the other overload just
// below instead. See this for details.
////////////////////////////////////////////////////////////
template <std::size_t... I>
inline constexpr auto StrToNullTerminatedArray(tstring_view str,
std::index_sequence<I...>) noexcept
{
///////////////////////////////////////////////////////////
// Creates a "std::array" and initializes it to all chars
// in "str" (just a fancy way to copy it but in the same
// order given by "I" - usually sequential), but also
// adding a NULL terminator as seen, guaranteeing the
// array is NULL terminated (so callers can safely depend
// on this if required - note that whether "str" itself is
// already NULL terminated is therefore irrelevant to us -
// harmless if it is but we just ignore it).
//
// Note BTW that we could rely on CTAD (Class Template
// Argument Deduction) in the following call if we wish,
// and therefore avoid explicitly passing the "std::array"
// template args in the return value as we're now doing
// (since it's more clear what's being returned IMHO). If
// we relied on CTAD instead then we could just return the
// following instead (less verbose than manually passing
// the "std::array" template args as we're now doing but
// again, it's not as clear what's being returned IMHO):
//
// return std::array{str[I]..., _T('\0')};
//
// The type of "std:array" would then resolve to the
// following, where "N" is the number of (std::size_t)
// integers in parameter pack "I", and there's one extra
// tchar for the NULL terminator we're manually adding
// (hence the "+ 1"):
//
// std::array<tchar, N + 1>;
//
// In either case, whether we explicitly pass the
// "std::array" template args or rely on CTAD, parameter
// pack expansion of "I" occurs as always so if "I" is
// {0, 1, 2} for instance (though it doesn't have to be in
// sequential order if a caller requires a different order
// but usually not), then it resolves to the following
// (again, note that we manually pass the NULL terminator
// as seen):
//
// return std::array<tchar, 4>{str[0], str[1], str[2], _T('\0')};
//
// Note that things also work if "I" is empty (so its size
// is zero), normally because "str" is empty so there's
// nothing to copy. In this case the call simply resolves
// to the following:
//
// return std::array<tchar, 1>{_T('\0')};
//
// The above array therefore stores the NULL terminator
// only (as would be expected).
///////////////////////////////////////////////////////////
return std::array<tchar, sizeof...(I) + 1>{str[I]..., _T('\0')};
}
////////////////////////////////////////////////////////////
// See other overload just above. The following overload
// just defers to it in order to convert "str" to a
// "std::array" which it then returns (with a NULL
// terminator always added - see overload above). Note
// that the following overload exists to clean up the
// syntax of the above overload a bit when you need to
// convert the entire "str" arg to a "std::array", as
// most will usually be doing (though to copy any other
// sequence of characters from "str" to a "std::array"
// you'll need to call the above overload directly
// instead, passing the specific indexes in "str" you
// wish to copy - very rare though).
//
// Note that as explained in the overload above, the
// syntax for both overloads is ugly due to "constexpr"
// shortcomings in current versions of C++. The following
// overload is nevertheless a cleaner version than the
// one above when you simply need to convert "str" to a
// "std::array" (again, as most will usually be doing).
//
// Example
// -------
// // Any "constexpr" string
// constexpr tstring_view str = _T("Testing");
//
// //////////////////////////////////////////////////
// // The type of "array" returned by the following
// // call is therefore the following:
// //
// // std::array<tchar, 8>
// //
// // Note that its size, 8, is the length of
// // "Testing" above, 7, plus 1 for the NULL
// // terminator, but the NULL terminator doesn't
// // originate from the one in the "Testing" string
// // literal above. Instead we always manually add
// // our own. "str" above therefore need not be NULL
// // terminated even though it is in this case
// // (since it's initialized from a string literal
// // above which does include one - if initialized
// // from another source that's not NULL terminated
// // however then it doesn't matter since we always
// // manually add our own).
// //
// // Note that as seen in the following call,
// // "str.size()" should always be passed as the 1st
// // template parameter to "StrToNullTerminatedArray()",
// // but having to do this is ugly. That is,
// // "StrToNullTerminatedArray()" can simply get
// // hold of this by invoking "size()" on the same
// // "str" parameter we're passing it, so having to
// // separately pass it as a template parameter is
// // really unnecessary. However, since the "size()"
// // member is required in a "constexpr" context and
// // C++ doesn't support "constexpr" function
// // arguments at this writing, the function can't
// // call "size()" on its "str" arg in a "constexpr"
// // context so we're forced to pass it as a
// // template parameter instead (for now - maybe C++
// // will support "constexpr" function parameters
// // one day)
// //////////////////////////////////////////////////
// constexpr auto strAsArray = StrToNullTerminatedArray<str.size()>(str);
////////////////////////////////////////////////////////////
template <std::size_t Size> // Always pass "str.size()"
inline constexpr auto StrToNullTerminatedArray(tstring_view str) noexcept
{
////////////////////////////////////////////////
// Defer to overload just above, passing all
// indexes that need to be copied from "str"
// into the resulting array (via the 2nd arg,
// i.e., zero to the number of indexes in "str"
// - 1). Note that the "Size" template arg
// we're passing to "std::make_index_sequence"
// must always be "str.size()" itself (callers
// should always call us this way - see
// function comments above)
//////////////////////////////////////////////////
return StrToNullTerminatedArray(str, std::make_index_sequence<Size>());
}
///////////////////////////////////////////////////////////////
// "TypeNameImplBase". Base class of "TypeNameImpl" that
// follows just after it. Latter class is a template but the
// following class isn't. Therefore contains functions that
// don't depend on template arg "T" of "TypeNameImpl" so no
// need to declare them there. "TypeNameImpl" simply inherits
// from the following class instead and defers to it to carry
// out the actual work (of extracting the type name for its
// "T" template arg from __PRETTY_FUNCTION__ or (on MSFT
// platforms) __FUNCSIG__)
///////////////////////////////////////////////////////////////
class TypeNameImplBase
{
protected:
//////////////////////////////////////////////////////////////////
// *** IMPORTANT ***
//
// Must be declared before 1st use or compilation will fail in
// some compilers (declaration order of function templates
// matters).
//
// GetPrettyFunction(). Returns the predefined string constant
// __PRETTY_FUNCTION__ or (for MSFT) __FUNCSIG__ (the latter if
// _MSC_VER is #defined so it also applies to non-Microsoft
// compilers running in Microsoft VC++ compatibility mode - see
// "Non-Microsoft compilers targeting Windows" further below).
// Returns these as a "tstring_view" which lives for the life of
// the app since it's just a view into the latter strings which
// are always statically defined (though compilers will normally
// remove these static strings from the final compiled binary if
// they determine the returned "tstring_view" is used in a
// compile-time only context - they're not used at runtime IOW so
// they can safely be removed).
//
// Assuming template arg "T" is a float for instance, each
// resolves to the following at this writing (where the first
// three rows show the offsets in the strings for your guidance
// only, and the rows just after show the actual value of the
// above strings for the compilers we currently support). Note
// that each string originates from __PRETTY_FUNCTION__ if
// _MSC_VER is not #defined or __FUNCSIG__ otherwise (note that
// when non-Microsoft compilers are running in Microsoft VC++
// compatibility mode then _MSC_VER will always be #defined so we
// rely on __FUNCSIG__ as noted, but __PRETTY_FUNCTION__ is still
// #defined by those compilers as well, even though the format
// differs from __FUNCSIG__ a bit - we just ignore
// __PRETTY_FUNCTION__ altogether in this case however):
// 1 1 1 1 1 1 1
// 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
// 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
// GCC (1 of 2 - described below): static constexpr auto StdExt::Private::TypeNameImplBase::GetPrettyFunction() [with T = float]
// GCC (2 of 2 - described below): static constexpr StdExt::tstring_view StdExt::Private::TypeNameImplBase::GetPrettyFunction() [with T = float; StdExt::tstring_view = std::basic_string_view<char>]
// Clang: static auto StdExt::Private::TypeNameImplBase::GetPrettyFunction() [T = float]
// Clang (if _MSC_VER #defined): static auto __cdecl StdExt::Private::TypeNameImplBase::GetPrettyFunction(void) [T = float]
// Intel: static auto StdExt::Private::TypeNameImplBase::GetPrettyFunction() [T = float]
// Intel (if _MSC_VER #defined): static auto __cdecl StdExt::Private::TypeNameImplBase::GetPrettyFunction(void) [T = float]
// Microsoft VC++: auto __cdecl StdExt::Private::TypeNameImplBase::GetPrettyFunction<float>(void) noexcept
//
// GCC
// ---
// Note that unlike the other compilers, two possible formats
// exist for GCC as seen above (the others have one), where the
// one used depends on the return type of "GetPrettyFunction()"
// itself. GCC format "1 of 2" above is the one currently in
// effect at this writing since "GetPrettyFunction()" currently
// returns "auto", which just resolves to
// "std::basic_string_view<tchar>" (unless someone changed the
// return type since this writing which is safely handled but
// read on). Because "GetPrettyFunction()" is *not* returning an
// alias for another type in this case (it's returning "auto"
// which is not treated as an alias by GCC), GCC uses format "1
// of 2". If the return type of "GetPrettyFunction()" is ever
// changed however so that it returns an alias instead, such as
// "tstring_view" seen in "2 of 2" above (which is an alias for
// "std::basic_string_view<tchar>" - note that tchar always
// resolves to "char" on GCC), then GCC uses format "2 of 2"
// above instead. In this case it includes (resolves) this alias
// at the end of __PRETTY_FUNCTION__ itself, as seen. This refers
// to the "tstring_view = std::basic_string_view<char>" portion
// of the "2 of 2" string above. It removes this portion of the
// string in "1 of 2" however since the return value of
// "GetPrettyFunction()" is not returning an alias in this case
// but "auto" (or you can change "auto" directly to
// "std::basic_string_view<tchar>" if you wish since it's still
// not an alias). The "tstring_view =
// std::basic_string_view<char>" portion of the "2 of 2" string
// is no longer required IOW since there's no "tstring_view"
// alias anymore so GCC removes it. The upshot is that the offset
// to the type we're trying to extract from __PRETTY_FUNCTION__,
// a "float" in the above example but whatever the type happens
// to be, can change depending on the return type of
// "GetPrettyFunction()" itself. Therefore, unlike the case for
// all other compilers we currently support, whose offset to the
// type is always at the same consistent location from the start
// of __PRETTY_FUNCTION__ or (when _MSC_VER is #defined)
// __FUNCSIG__, we need to check which format is in effect for
// GCC in order to protect against possible changes to the return
// type of "GetPrettyFunction()" itself. "GetTypeNameOffset()",
// which calculates the offset of "T" into the string (offset of
// "float" in the above examples), therefore takes this situation
// into account for GCC only (checking which of these two formats
// is in effect).
//
// Reliance on "float" to calculate the offset and length
// ------------------------------------------------------
// Note that float isn't just used for the examples above, it's
// also used by members "GetTypeNameOffset()" and
// "GetTypeNameLen()" below to determine the offset into each
// string of the type name to extract (whatever template arg "T",
// resolves to), and the type's length, both of which are called
// by member "ExtractTypeNameFromPrettyFunction()" below to
// extract the type name from the string. Since type float always
// returns "float" for all compilers we support (which will
// likely be the case for any future compiler as well normally,
// though any type whose string is the same for all supported
// compilers will do - read on), we can leverage this knowledge
// to easily determine the offset of type "T" in the string and
// its length, regardless of what "T" is, without any complicated
// parsing of the above strings (in order to locate "T" within
// the string and determine its length). Note that parsing would
// be a much more complicated approach because type "T" itself
// can potentially contain any character including angled
// brackets, square brackets, equal signs, etc., each of which
// makes it harder to distinguish between those particular
// characters in "T" itself from their use as delimiters in the
// actual "pretty" strings (when they aren't part of "T"). It
// would usually be rare in practice but can happen. For
// instance, "T" might be a template type (class) called, say,
// "Bracket", with a non-type template arg of type "char" so
// "Bracket" can be instantiated (and therefore appear in
// __PRETTY_FUNCTION__ or __FUNCSIG__) like so (ignoring the
// class' namespace if any to keep things simple):
//
// Bracket<']'>
// Bracket<'>'>
// Bracket<'='>
// Bracket<';'>
//
// This makes it more difficult to parse __PRETTY_FUNCTION__ and
// __FUNCSIG__ looking for the above type and determining its
// length because the above strings contain the same characters
// (potentially) used as delimiters elsewhere in
// __PRETTY_FUNCTION__ and __FUNCSIG__ (where applicable), so any
// parsing code would have to deal with this as required (i.e.,
// distinguishing between these characters in "T" itself and
// delimiters elsewhere in the "pretty" string is non-trivial).
// For example, determining if ']' is part of the type itself or
// an actual delimiter in __PRETTY_FUNCTION__ is less simple than
// it first appears (not rocket science but a pain nevertheless).
//
// To circumvent having to do this (parse the string to deal with
// this issue), there's a much easier alternative. For the offset
// of "T" itself, note that it's always the same within each
// supported compiler regardless of "T" (i.e., the same within
// GCC, the same within Microsoft, the same within Clang, etc. -
// it will normally be different for each particular compiler of
// course but all that matters for our purposes is that it's the
// same within each one). Within each particular compiler we can
// therefore simply rely on a known type like "float" to
// determine it (for that compiler), not "T" itself (since the
// offset to "T" for any particular compiler will be the same as
// the offset to "float" or any other type for that matter
// (within that compiler), so we can arbitrarily just rely on
// "float" - more on why "float" was chosen later).
//
// While the offset to "T" within the "pretty" string is always
// identical no matter what "T" is (within each compiler), the
// length of "T" itself will differ of course (depending on what
// "T" is). However, we know that for any two different types of
// "T", say "int" and "float" (any two types will do), the
// "pretty" string containing "int" will be identical to the
// "pretty" containing "float" except for the difference between
// "int" and "float" themselves. That's the only difference
// between these "pretty" strings, i.e., one contains "int" and
// one contains "float", but the remainder of the "pretty"
// strings are identical. Therefore, since the string "int" is 3
// characters long and the string "float" is 5 characters long (2
// characters longer than "int"), then the length of the "pretty"
// string containing "int" must be shorter than the "pretty"
// string containing "float" by 2 characters, since the strings
// themselves are identical except for the presence of "int" and
// "float" (within their respective strings). So by simply
// subtracting the length of the "pretty" string for a "float"
// from the length of the "pretty" string for any type "T" (i.e.,
// by simply computing this delta), we know how much longer
// (positive delta) or shorter (negative delta) the length of "T"
// must be compared to a "float", since the latter is always 5
// characters long. We therefore just add this (positive or
// negative) delta to 5 to arrive at the length of "T" itself.
// For an "int" for instance, the delta is -2 (the length of its
// "pretty" string minus the length of the "pretty" string for
// "float" is always -2), so 5 - 2 = 3 is the length of an "int".
// For a type longer than "float" it works the same way only the
// delta is positive in this case. If "T" is an "unsigned int"
// for instance then the delta is 7 (the length of its "pretty"
// string minus the length of the "pretty" string for "float" is
// always 7), so 5 + 7 = 12 is the length of "unsigned int". We
// can do this for any arbitrary "T" of course to get its own
// length. We simply compute the delta for its "pretty" string in
// relation to the "pretty" string for a "float" as described.
//
// Note that float was chosen over other types we could have used
// since the name it generates in its own pretty string is always
// "float" for all our supported compilers. It's therefore
// consistent among all supported compilers and its length is
// always 5, both situations making it a (normally) reliable type
// to work with in our code. In practice however all the
// fundamental types or (possibly) some user-defined type could
// have been used (each of which normally generates the same
// consistent string as well), but going forward "float" seemed
// (potentially) less vulnerable to issues like signedness among
// integral types for instance, or other possible issues. If an
// integral type like "int" was chosen instead for instance (or
// "char", or "long", etc.), some future compiler (or perhaps
// some compiler option) might display it as "signed int" or
// "unsigned int" instead, depending on the default signedness in
// effect (and/or some compiler option but regardless, it might
// not result in a consistent string among all compilers we
// support or might support in the future).
//
// Similarly, if we chose "double" instead it might be displayed
// as "double" as you would normally expect, but "long double"
// might also be possible based on some compiler option (so the
// string "double" may not be consistent either).
//
// Or if "bool" were chosen it might normally appear as "bool"
// but "unsigned char" might be possible too if some backwards
// compatibility option is turned on for a given compiler (since
// bools may have been internally declared that as "unsigned
// char" once-upon-a-time and someone may turn the option on for
// backwards compatibility reasons if required).
//
// In reality it doesn't seem likely this is actually going to
// happen for any of the fundamental types however, and even
// "float" itself could potentially become a "double" (or
// whatever) under some unknown circumstance but for now it seems
// to be potentially more stable than the other fundamental types
// so I chose it for that reason only (even if these reasons are
// a bit flimsy). It normally works.
//
// Non-Microsoft compilers targeting Windows
// -----------------------------------------
// Note that non-Microsoft compilers targeting Windows will
// #define _MSC_VER just like the Microsoft compiler does (VC++),
// indicating they're running in Microsoft VC++ compatibility
// mode (i.e., will compile VC++ Windows applications). For most
// intents and purposes we can therefore (usually) just test if
// _MSC_VER is #defined throughout our code and if true it means
// that either VC++ is running or some other non-Microsoft
// compiler is but it's running as if it were VC++. We can
// therefore usually just ignore the fact that it's not the real
// VC++ itself since it's running as if it were (so we can just
// carry on as if the real VC++ is running). However, instead of
// checking _MSC_VER we can also explicitly check the actual
// (real) compiler that's running by using our own #defined
// constants MICROSOFT_COMPILER, CLANG_COMPILER and
// INTEL_COMPILER instead (the only compilers we currently
// support that might #define _MSC_VER - MICROSOFT_COMPILER
// itself refers to the real VC++ compiler so it always #defines
// it but the other two only #define it when running in Microsoft
// compatibility mode). For many (actually most) purposes
// however we can just check _MSC_VER as described above when we
// don't care whether it's the real VC++ compiler
// (MICROSOFT_COMPILER #defined) vs the Clang compiler
// (CLANG_COMPILER #defined) or the Intel compiler
// (INTEL_COMPILER #defined), where the latter two cases simply
// mean these non-Microsoft compilers are running in Microsoft
// VC++ compatibility mode (since _MSC_VER is also #defined).
//
// Unfortunately the behavior of these non-Microsoft compilers
// isn't always 100% compatible with the actual (real) Microsoft
// compiler itself however. In particular, for the purposes of
// the function you're now reading, note that the code relies on
// the predefined Microsoft macro __FUNCSIG__ instead of
// __PRETTY_FUNCTION__ whenever _MSC_VER is #defined, so the
// actual (real) compiler doesn't matter to us (since __FUNCSIG__
// is also #defined for non-Microsoft compilers running in
// Microsoft VC++ compatibility mode). However, __FUNCSIG__ isn't
// a string literal or even a macro when using non-Microsoft
// compilers, unlike when using the actual Microsoft VC++
// compiler itself. It normally should be a string literal even
// on non-Microsoft compilers (if they were 100% compatible with
// VC++) but unfortunately it's not. It's just an array of
// "const" char when using non-Microsoft compilers but not an
// actual string literal. We therefore normally shouldn't be able
// to apply the native Microsoft macro _T to it since _T is just
// the C++ string literal prefix "L" when compiling for UTF-16 in
// Windows (usually the case), and the "L" prefix can only be
// applied to string literals. See the following for details
// (also consult _T in the MSFT docs if you're not already
// familiar with it):
//
// Wide string literals
// https://learn.microsoft.com/en-us/cpp/cpp/string-and-character-literals-cpp?view=msvc-170#wide-string-literals
//
// Since the "L" prefix can only be applied to string literals
// then it shouldn't work with __FUNCSIG__ when using
// non-Microsoft compilers, since the latter constant isn't a
// string literal in non-Microsoft compilers as described. It's
// only a string literal when using VC++ itself (as officially
// documented by MSFT), so "L" should only work when compiling
// with VC++. However, it turns out that "L" does work with
// __FUNCSIG__ in non-Microsoft compilers, at least the ones we
// support, even though it's not a string literal in those
// compilers. While I'm not sure why without further digging
// (read on), some type of special work-around was apparently
// introduced by these particular compiler vendors to handle
// __FUNCSIG__ as a string literal even though it's apparently
// not defined that way (and presumably applicable to other
// Microsoft predefined constants in these non-Microsoft
// compilers as well I assume, though it doesn't impact out
// situation here). See here for details about the situation but
// it requires reading through the details to better understand
// the fix (I haven't myself):
//
// Clang + -fms-extensions: __FUNCSIG__ is not a literal
// https://github.com/llvm/llvm-project/issues/114
//
// /////////////////////////////////////////////////////////
// // The issue is then cited as closed by "aeubanks" in
// // the above link (git commit 856f384), but he later
// // indicates that "patch was reverted, reopening", only
// // to close it again later on (git commit 878e590, which
// // the following links to). Apparently that was the
// // final fix.
// /////////////////////////////////////////////////////////
// https://github.com/llvm/llvm-project/commit/878e590503dff0d9097e91c2bec4409f14503b82
//
// Also see the following link which shows changes in a file called
// "clang/docs/ReleaseNotes.rst"
//
// [clang] Make predefined expressions string literals under -fms-extensions
// https://reviews.llvm.org/rG856f384bf94513c89e754906b7d80fbe5377ab53
//
// There's a comment in the "clang/docs/ReleaseNotes.rst" file
// itself that indicates:
//
// "Some predefined expressions are now treated as string
// literals in MSVC compatibility mode."
//
// I haven't looked into the situation in detail but the upshot
// is that in the code just below we rely on __FUNCSIG__ whenever
// _MSC_VER is #defined regardless of the compiler, and then
// apply the _T macro to __FUNCSIG__ as seen, which works even
// when using non-Microsoft compilers whose __FUNCSIG__ isn't a
// string literal (in the compilers we currently support). It's