Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ToString.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Characters_ToString_h_
5#define _Stroika_Foundation_Characters_ToString_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <chrono>
10#include <ios>
11#include <optional>
12#include <queue>
13#include <sstream>
14#include <stack>
15#include <thread>
16#include <tuple>
17#include <typeindex>
18#include <typeinfo>
19#include <utility>
20
22#include "Stroika/Foundation/Common/Concepts.h"
23#include "Stroika/Foundation/Common/CountedValue.h"
24#include "Stroika/Foundation/Common/KeyValuePair.h"
26
27#if __cpp_lib_stacktrace >= 202011 && !qCompiler_clangNotCompatibleWithLibStdCPPStackTrace_Buggy
28#include <stacktrace>
29#endif
30
31/**
32 * \file
33 * Convert arbitrary objects to String form, for the purpose of debugging (not reversible).
34 *
35 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
36 *
37 * TODO:
38 * @todo ToString(tuple) should use variadic templates and support multiple (past 3) args
39 */
40
41#if qStroika_HasComponent_fmtlib && (FMT_VERSION >= 110000)
43 struct GUID;
44}
46 class VariantValue;
47}
48namespace Stroika::Foundation::Memory {
49 class BLOB;
50}
51#endif
52
54
55 /**
56 * \brief Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except for debugging
57 *
58 * Convert an instance of the given object to a printable string representation. This representation
59 * is not guaranteed, pretty, or parsable. This feature is generally for debugging purposes, but can be used
60 * to render/emit objects in any informal setting where you just need a rough sense of the object (again,
61 * the only case I can think of here would be for debugging).
62 *
63 * \note *AKA*
64 * o DUMP
65 * o PrettyPrint
66 *
67 * Patterned after:
68 * Java:
69 * From the Object.toString() docs:
70 * Returns a string representation of the object. In general, the toString method
71 * returns a string that "textually represents" this object.
72 * Javascript:
73 * The toString() method returns a string representing object.
74 * Every object has a toString() method that is automatically called when the object is
75 * to be represented as a text value or when an object is referred to in a manner in which
76 * a string is expected.
77 *
78 * \note Built-in or std types intrinsically supported:
79 * o type_index, type_traits, or anything with a .name () which returns a const SDKChar* string.
80 * o is_array<T>
81 * o is_enum<T>
82 * o atomic<T> where T is a ToStringable type
83 * o shared_ptr<T> (but not unique_ptr<> because its not regular, which std::format requires)
84 * o std::exception
85 * o std::tuple
86 * o std::variant
87 * o std::pair
88 * o std::optional
89 * o std::wstring
90 * o std::filesystem::path
91 * o exception_ptr
92 * o POD types (int, bool, double, etc)
93 * o anything with .begin (), .end () - so any container/iterable
94 * o anything class(or struct) with a ToString () method
95 *
96 * \note Other types automatically supported
97 * o KeyValuePair
98 * o CountedValue
99 * o String -- printed as 'the-string' (possibly length limited)
100 *
101 * \note Extra arguments/overloads
102 * o ToString (float, FloatConversion::ToStringOptions) // any floating point argument
103 * o ToString (float, FloatConversion::Precision) // ''
104 * o ToString (integral T, ios_base::fmtflags flags); // flags may be std::dec, std::oct, or std::hex
105 * // @see https://en.cppreference.com/w/cpp/io/ios_base/fmtflags
106 * o ToString (Duration, FloatConversion::Precision) // forwards to Duration::As<String> (Precision)
107 *
108 * \note *Implementation Note*
109 * This implementation defaults to calling T{}.ToString ().
110 *
111 * \note @see IToString to check if Characters::ToString () well defined.
112 */
113 template <typename T, typename... ARGS>
114 String ToString (T&& t, ARGS... args);
115
116 /**
117 * \brief Check if legal to call Characters::ToString(T)...
118 */
119 template <typename T>
120 concept IToString = requires (T t) {
121 { ToString (t) } -> convertible_to<Characters::String>;
122 };
123
124 /**
125 * \brief same as ToString()/1 - but without the potentially confusing multi-arg overloads (confused some template expansions).
126 */
127 template <typename T>
128 String UnoverloadedToString (const T& t);
129
130 //// @todo read https://stackoverflow.com/questions/59909102/stdformat-of-user-defined-types
131 // I THINK I want to SUBCLASS from std::formatter<std::string> - or something close to that...
132 /**
133 * define a 'std::formatter' for Stroika (and other types) for which formatter not already predefined - which use
134 * Characters::ToString (T)
135 */
136 template <IToString T>
137 struct ToStringFormatter /* : std::formatter<std::wstring>*/ {
138 qStroika_Foundation_Characters_FMT_PREFIX_::formatter<String, wchar_t> fDelegate2_;
139
140 template <class ParseContext>
141 constexpr typename ParseContext::iterator parse (ParseContext& ctx)
142 {
143 return fDelegate2_.parse (ctx);
144 }
145
146 template <class FmtContext>
147 typename FmtContext::iterator format (T s, FmtContext& ctx) const
148 {
149 return fDelegate2_.format (UnoverloadedToString (s), ctx);
150#if 0
151
152 std::wstringstream out;
153 out << UnoverloadedToString (s);
154#if __cpp_lib_ranges >= 202207L
155 return std::ranges::copy (std::move (out).str (), ctx.out ()).out;
156#else
157 return Common::StdCompat::format_to (ctx.out (), L"{}", String{out.str ()});
158#endif
159#endif
160 }
161 };
162 template <IToString T>
163 struct ToStringFormatterASCII {
164
165 ToStringFormatter<T> fDelegate2_;
166 template <class ParseContext>
167 constexpr typename ParseContext::iterator parse (ParseContext& ctx)
168 {
169 // Not clear how to forward...
170 auto it = ctx.begin ();
171 while (it != ctx.end ()) {
172 ++it;
173#if 0
174 if (it == ctx.end ()) {
175 throw Common::StdCompat::format_error{"Invalid format args (missing }) for ToStringFormatterASCII."};
176 }
177#endif
178 if (*it == '}') {
179 return it;
180 }
181 }
182 return it;
183 }
184
185 template <class FmtContext>
186 typename FmtContext::iterator format (T s, FmtContext& ctx) const
187 {
188 using namespace Stroika::Foundation::Characters;
189 std::wstringstream out;
190 out << UnoverloadedToString (s);
191
192 // @todo delegate to string version so we can use its ignore errors code......
193#if __cpp_lib_ranges >= 202207L
194 return std::ranges::copy (String{out.str ()}.AsNarrowSDKString (eIgnoreErrors), ctx.out ()).out;
195#else
196 return Common::StdCompat::format_to (ctx.out (), "{}", String{out.str ()}.AsNarrowSDKString (eIgnoreErrors));
197#endif
198 }
199 };
200
201}
202
203namespace Stroika::Foundation::Characters::Private_ {
204
205 /**
206 * \brief IStdFormatterPredefinedFor_<T> = what formattable<T,wchar_t> WOULD have returned if it could be evaluated safely without its value being memoized.
207 *
208 * \see https://stackoverflow.com/questions/78774217/how-to-extend-stdformatter-without-sometimes-introducing-conflicts-can-concep
209 *
210 * \see https://en.cppreference.com/w/cpp/utility/format/formatter std::formatter predefined for
211 *
212 * template <typename T, typename TUnique = decltype ([] () -> void {})>
213 * concept IStdFormatterPredefinedFor_ = requires (T t) {
214 * typename TUnique;
215 * Stroika::Foundation::Common::StdCompat::formattable<T, wchar_t>;
216 * };
217 */
218 template <typename T>
220 // clang-format off
221
222 // C++-20
224 or requires { []<typename TRAITS, typename ALLOCATOR> (type_identity<std::basic_string<char, TRAITS, ALLOCATOR>>) {}(type_identity<T> ()); }
225 or requires { []<typename TRAITS, typename ALLOCATOR> (type_identity<std::basic_string<wchar_t, TRAITS, ALLOCATOR>>) {}(type_identity<T> ()); }
226 or requires { []<typename TRAITS> (type_identity<std::basic_string_view<char, TRAITS>>) {}(type_identity<T> ()); }
227 or requires { []<typename TRAITS> (type_identity<std::basic_string_view<wchar_t, TRAITS>>) {}(type_identity<T> ()); }
228 or requires { []<size_t N> (type_identity<wchar_t[N]>) {}(type_identity<T> ()); }
229 or std::is_arithmetic_v<T>
230 or Common::IAnyOf<decay_t<T>, nullptr_t, void*, const void*>
231 // chrono
232 or Common::IDuration<T>
233 or requires { []<typename DURATION> (type_identity<std::chrono::sys_time<DURATION>>) {}(type_identity<T> ()); }
234#if !defined(_LIBCPP_VERSION) or _LIBCPP_VERSION > 199999
235 or requires { []<typename DURATION> (type_identity<std::chrono::utc_time<DURATION>>) {}(type_identity<T> ()); }
236 or requires { []<typename DURATION> (type_identity<std::chrono::tai_time<DURATION>>) {}(type_identity<T> ()); }
237 or requires { []<typename DURATION> (type_identity<std::chrono::gps_time<DURATION>>) {}(type_identity<T> ()); }
238#endif
239 or requires { []<typename DURATION> (type_identity<std::chrono::file_time<DURATION>>) {}(type_identity<T> ()); }
240 or requires { []<typename DURATION> (type_identity<std::chrono::local_time<DURATION>>) {}(type_identity<T> ()); }
241 or Common::IAnyOf<decay_t<T>, chrono::day, chrono::month, chrono::year,
242 chrono::weekday, chrono::weekday_indexed, chrono::weekday_last,
243 chrono::month_day, chrono::month_day_last, chrono::month_weekday, chrono::month_weekday_last,
244 chrono::year_month, chrono::year_month_day, chrono::year_month_day_last, chrono::year_month_weekday,chrono::year_month_weekday_last
245#if (not defined (_GLIBCXX_RELEASE) or _GLIBCXX_RELEASE > 12) and (!defined(_LIBCPP_VERSION) or _LIBCPP_VERSION > 199999)
246 , chrono::sys_info, chrono::local_info
247#endif
248 >
249 or requires { []<typename DURATION> (type_identity<chrono::hh_mm_ss<DURATION>>) {}(type_identity<T> ()); }
250#if (not defined (_GLIBCXX_RELEASE) or _GLIBCXX_RELEASE > 12) and (!defined(_LIBCPP_VERSION) or _LIBCPP_VERSION > 199999)
251 or requires { []<typename DURATION, typename TimeZonePtr> (type_identity<chrono::zoned_time<DURATION, TimeZonePtr>>) {}(type_identity<T> ()); }
252#endif
253
254 // C++23
255 //
256 // sadly MSFT doesn't support __cplusplus with right value
257 // 202302L is right value (of __cplusplus) to check for C++ 23, but 202101L needed for clang++16 ;-(
258 // value with clang++16 was 202101L and cpp2b and libc++ (ubuntu 23.10 and 24.04) flag... and it had at least the pair<> code supported.
259 // this stuff needed for clang++-18-debug-libstdc++-c++23
260 //
261#if __cpp_lib_format_ranges
262 or ranges::range<decay_t<T>>
263#endif
264#if (__cplusplus > 202101L or _LIBCPP_STD_VER >= 23 or (_MSVC_LANG >= 202004 and _MSC_VER >= _MSC_VER_2k22_17Pt11_)) and not (defined (_GLIBCXX_RELEASE) and _GLIBCXX_RELEASE <= 14)
265 or Common::IPair<remove_cvref_t<T>> or Common::ITuple<remove_cvref_t<T>>
266#endif
267#if __cplusplus > 202101L or _LIBCPP_STD_VER >= 23 or _MSVC_LANG >= 202004
268 or Common::IAnyOf<remove_cvref_t<T>, thread::id>
269#endif
270#if __cpp_lib_stacktrace >= 202011 && !qCompiler_clangNotCompatibleWithLibStdCPPStackTrace_Buggy
271 or Common::IAnyOf<remove_cvref_t<T>, stacktrace_entry>
272 or requires { []<typename ALLOCATOR> (type_identity<basic_stacktrace<ALLOCATOR>>) {}(type_identity<T> ()); }
273#endif
274#if __cplusplus > 202101L or _LIBCPP_STD_VER >= 23 or _MSVC_LANG >= 202004
275 or requires { []<typename TT> (type_identity<stack<TT>>) {}(type_identity<T> ()); }
276 or requires { []<typename TT> (type_identity<queue<TT>>) {}(type_identity<T> ()); }
277#endif
278
279 // C++26
280#if __cplusplus > 202400L or _LIBCPP_STD_VER >= 26 or _MSVC_LANG >= 202400L
281 // unsure what to check - __cpp_lib_format - test c++26 __cpp_lib_formatters < 202601L -- 202302L is c++23
282 or Common::IAnyOf<remove_cvref_t<T>, std::filesystem::path>
283#endif
284
285
286
287 // AND throw in other libraries Stroika is built with (this is why the question in https://stackoverflow.com/questions/78774217/how-to-extend-stdformatter-without-sometimes-introducing-conflicts-can-concep
288 // is so important to better resolve!
289#if qStroika_HasComponent_fmtlib
290 or Common::IAnyOf<decay_t<T>, qStroika_Foundation_Characters_FMT_PREFIX_::string_view, qStroika_Foundation_Characters_FMT_PREFIX_::wstring_view>
291#if (FMT_VERSION >= 110000)
292 // Workaround issue with fmtlib 11 ranges support - it matches these
293 or Common::IAnyOf<decay_t<T>,Common::GUID, Memory::BLOB>
294 // FMT_VERSION has pair/tuple support after FMT version 11
295 or Common::IPair<remove_cvref_t<T>> or Common::ITuple<remove_cvref_t<T>>
296#endif
297#endif
298
299 ;
300 // clang-format on
301
302// Debug hack to spot-check IStdFormatterPredefinedFor_
303#if _MSVC_LANG == 202004 && (_MSC_VER < _MSC_VER_2k22_17Pt11_)
304 static_assert (not IStdFormatterPredefinedFor_<std::pair<int, char>>);
305 static_assert (not IStdFormatterPredefinedFor_<std::tuple<int>>);
306 static_assert (IStdFormatterPredefinedFor_<std::thread::id>);
307 static_assert (not IStdFormatterPredefinedFor_<std::type_index>);
308 static_assert (not IStdFormatterPredefinedFor_<std::exception_ptr>);
309#endif
310#if _MSVC_LANG == 202004 && (_MSC_VER == _MSC_VER_2k22_17Pt11_)
311 static_assert (IStdFormatterPredefinedFor_<std::pair<int, char>>);
312 static_assert (IStdFormatterPredefinedFor_<std::tuple<int>>);
313 static_assert (IStdFormatterPredefinedFor_<std::thread::id>);
314 static_assert (not IStdFormatterPredefinedFor_<std::type_index>);
315 static_assert (not IStdFormatterPredefinedFor_<std::exception_ptr>);
316#endif
317#if __cplusplus == 202002L && _GLIBCXX_RELEASE == 13
318 static_assert (not IStdFormatterPredefinedFor_<std::pair<int, char>>);
319 static_assert (not IStdFormatterPredefinedFor_<std::tuple<int>>);
320 static_assert (not IStdFormatterPredefinedFor_<std::thread::id>);
321 static_assert (not IStdFormatterPredefinedFor_<std::type_index>);
322 static_assert (not IStdFormatterPredefinedFor_<std::exception_ptr>);
323#endif
324#if __cplusplus == 202002L && _GLIBCXX_RELEASE == 14
325 static_assert (not IStdFormatterPredefinedFor_<std::pair<int, char>>);
326 static_assert (not IStdFormatterPredefinedFor_<std::tuple<int>>);
327 static_assert (not IStdFormatterPredefinedFor_<std::thread::id>);
328 static_assert (not IStdFormatterPredefinedFor_<std::type_index>);
329 static_assert (not IStdFormatterPredefinedFor_<std::exception_ptr>);
330#endif
331#if __cplusplus == 202302L && _GLIBCXX_RELEASE == 14
332 static_assert (not IStdFormatterPredefinedFor_<std::pair<int, char>>);
333 static_assert (not IStdFormatterPredefinedFor_<std::tuple<int>>);
334 static_assert (IStdFormatterPredefinedFor_<std::thread::id>);
335 static_assert (not IStdFormatterPredefinedFor_<std::type_index>);
336 static_assert (not IStdFormatterPredefinedFor_<std::exception_ptr>);
337#endif
338#if defined(__APPLE__) && __clang_major__ == 15
339 static_assert (not IStdFormatterPredefinedFor_<std::pair<int, char>>);
340 static_assert (not IStdFormatterPredefinedFor_<std::tuple<int>>);
341 static_assert (not IStdFormatterPredefinedFor_<std::thread::id>);
342 static_assert (not IStdFormatterPredefinedFor_<std::type_index>);
343 static_assert (not IStdFormatterPredefinedFor_<std::exception_ptr>);
344#endif
345
346// Debugging hacks to make sure IStdFormatterPredefinedFor_ defined properly
347//
348// CRAZY - but cannot check (at least on visual studio) here: checking NOW causes
349// this to FAIL later (i guess compiler caches results cuz thinks its constant).
350// if this worked, I'd add more static_asserts to check...
351//
352// Just use briefly - to debug IStdFormatterPredefinedFor_ - to verify we fail AFTER this point;
353// enable #if below and just look if these static_asserts fail - ignore any issues which come after (which is why this cannot be left #if 1)
354#if 0
355 static_assert (IStdFormatterPredefinedFor_<std::type_index> == Common::StdCompat::formattable<std::type_index, wchar_t>);
356 static_assert (IStdFormatterPredefinedFor_<std::pair<int, char>> == Common::StdCompat::formattable<std::pair<int, char>, wchar_t>);
357 static_assert (IStdFormatterPredefinedFor_<std::tuple<int>> == Common::StdCompat::formattable<std::tuple<int>, wchar_t>);
358 static_assert (IStdFormatterPredefinedFor_<std::thread::id> == Common::StdCompat::formattable<std::thread::id, wchar_t>);
359 static_assert (IStdFormatterPredefinedFor_<std::filesystem::path> == Common::StdCompat::formattable<std::filesystem::path, wchar_t>);
360 static_assert (IStdFormatterPredefinedFor_<std::exception_ptr> == Common::StdCompat::formattable<std::exception_ptr, wchar_t>);
361#endif
362
363 /*
364 * \brief roughly !formattable<T> and IToString<T> ; but cannot do this cuz then formattable<T> would change meaning. So really mean 'formattable so far'
365 *
366 * \see https://en.cppreference.com/w/cpp/utility/format/formatter
367 * \see https://stackoverflow.com/questions/78774217/how-to-extend-stdformatter-without-sometimes-introducing-conflicts-can-concep
368 */
369 template <typename T>
370 concept IUseToStringFormatterForFormatter_ =
371
372 // If Characters::ToString() would work
373 IToString<T>
374
375#if !qCompiler_IUseToStringFormatterForFormatter_Buggy
376 // But NOT anything std c++ defined to already support (else we get ambiguity error)
377 and not IStdFormatterPredefinedFor_<T>
378#else
379 and (requires (T t) {
380 { t.ToString () } -> convertible_to<Characters::String>;
381 } or Common::IKeyValuePair<remove_cvref_t<T>> or Common::ICountedValue<remove_cvref_t<T>>
382 //or Common::ISharedPtr<decay_t<T>>
383#if !__cpp_lib_format_ranges
384#if !qStroika_HasComponent_fmtlib or (FMT_VERSION < 110000)
385 or (ranges::range<decay_t<T>> and
386 not Common::IAnyOf<decay_t<T>, string, wstring, string_view, wstring_view, const char[], const wchar_t[],
387 qStroika_Foundation_Characters_FMT_PREFIX_::string_view, qStroika_Foundation_Characters_FMT_PREFIX_::wstring_view>)
388#endif
389#endif
390#if _MSC_VER || __cplusplus < 202101L /*202302L 202100L 202300L*/ || (__clang__ != 0 && __GLIBCXX__ != 0 && __GLIBCXX__ <= 20240908) || \
391 (!defined(__clang__) && __cplusplus == 202302L && __GLIBCXX__ <= 20240908) and (!defined(_LIBCPP_STD_VER) || _LIBCPP_STD_VER < 23)
392#if !qStroika_HasComponent_fmtlib or (FMT_VERSION < 110000)
393 // available in C++23
394 or Common::IPair<remove_cvref_t<T>> or
395 Common::ITuple<remove_cvref_t<T>>
396#endif
397#endif
398#if (!defined(__cpp_lib_formatters) || __cpp_lib_formatters < 202302L) and (!defined(_LIBCPP_STD_VER) || _LIBCPP_STD_VER < 23)
399 // available in C++23
400 or Common::IAnyOf<remove_cvref_t<T>, thread::id>
401#endif
402#if __cplusplus < 202400L || (defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE <= 14)
403 or Common::IAnyOf<remove_cvref_t<T>, std::filesystem::path>
404#endif
405 or is_enum_v<remove_cvref_t<T>> or Common::IOptional<remove_cvref_t<T>> or Common::IVariant<remove_cvref_t<T>> or
406 same_as<T, std::chrono::time_point<chrono::steady_clock, chrono::duration<double>>> or
407 Common::IAnyOf<remove_cvref_t<T>, exception_ptr, type_index> or derived_from<T, exception> or Common::ISharedPtr<T>);
408#endif /*qCompiler_IUseToStringFormatterForFormatter_Buggy*/
409 ;
410
411}
412
413/**
414 * add ToStringFormatter to the std::formatter object - so all std::format (and Stroika Format, and _f etc) format calls will
415 * apply ToString() as appropriate.
416 *
417 * And a few static_asserts to verify this is working as expected.
418 *
419 * This should allow all the formattable features of up to C++26 - to work on compilers with older settings (such as C++20, or c++23)
420 * where possible (like cannot format stacktrace if it its impl doesn't exist, but can always format filesystem::path - just using Stroika
421 * formatter instead of std-c++ formatter).
422 */
423template <Stroika::Foundation::Characters::Private_::IUseToStringFormatterForFormatter_ T>
424struct qStroika_Foundation_Characters_FMT_PREFIX_::formatter<T, wchar_t> : Stroika::Foundation::Characters::ToStringFormatter<T> {};
425template <Stroika::Foundation::Characters::Private_::IUseToStringFormatterForFormatter_ T>
426struct qStroika_Foundation_Characters_FMT_PREFIX_::formatter<T, char> : Stroika::Foundation::Characters::ToStringFormatterASCII<T> {};
427
428#if qCompilerAndStdLib_StdFmtOfPath_Buggy
429template <>
430struct qStroika_Foundation_Characters_FMT_PREFIX_::formatter<std::filesystem::path, wchar_t>
431 : Stroika::Foundation::Characters::ToStringFormatter<std::filesystem::path> {};
432template <>
433struct qStroika_Foundation_Characters_FMT_PREFIX_::formatter<std::filesystem::path, char>
434 : Stroika::Foundation::Characters::ToStringFormatterASCII<std::filesystem::path> {};
435#endif
436
437/*
438 * If any of these static_asserts trigger, it means you are using a newer compiler I don't have
439 * proper IUseToStringFormatterForFormatter_ or IStdFormatterPredefinedFor_ settings for. Adjust those settings above so these tests pass.
440 * (or if qCompiler_IUseToStringFormatterForFormatter_Buggy - clang++ - then see IUseToStringFormatterForFormatter_ directly)
441 */
442static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::exception_ptr, wchar_t>);
443static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::filesystem::path, wchar_t>);
444static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::optional<int>, wchar_t>);
445static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::shared_ptr<int>, wchar_t>); // but not unique_ptr cuz not regular, even though supported by ToString()
446static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::pair<int, char>, wchar_t>);
447static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::thread::id, wchar_t>);
448static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::type_index, wchar_t>); // note not type_info (result of typeid) - because formattable requires copyable, and type_info not copyable
449#if !qCompilerAndStdLib_FormatThreadId_Buggy
450static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::thread::id, wchar_t>);
451#endif
452#if qCompilerAndStdLib_formattable_of_tuple_Buggy
454#else
455static_assert (Stroika::Foundation::Common::StdCompat::formattable<std::tuple<int>, wchar_t>);
456#endif
457// true, but don't #include just for this
458//static_assert (Stroika::Foundation::Common::StdCompat::formattable<Time::TimePointInSeconds, wchar_t>);
459//static_assert (Stroika::Foundation::Common<Stroika::Foundation::IO::Network::URI, wchar_t>);
460
461/*
462 ********************************************************************************
463 ***************************** Implementation Details ***************************
464 ********************************************************************************
465 */
466
467#include "ToString.inl"
468
469#endif /*_Stroika_Foundation_Characters_ToString_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual string AsNarrowSDKString() const
Definition String.inl:830
Check if legal to call Characters::ToString(T)...
Definition ToString.h:120
IStdFormatterPredefinedFor_<T> = what formattable<T,wchar_t> WOULD have returned if it could be evalu...
Definition ToString.h:219
concept - trivial shorthand for variadic same_as A or same_as B, or ...
Definition Concepts.h:175
Concept ITuple<T> check if T is a tuple.
Definition Concepts.h:298
String UnoverloadedToString(const T &t)
same as ToString()/1 - but without the potentially confusing multi-arg overloads (confused some templ...
Definition ToString.inl:476
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
Definition ToString.inl:465
STL namespace.