Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
ToString.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include <chrono>
5#include <exception>
6#include <filesystem>
7#include <functional>
8#include <ranges>
9#include <thread>
10#include <typeindex>
11#include <typeinfo>
12#include <variant>
13#include <wchar.h>
14
15#include "Stroika/Foundation/Characters/FloatConversion.h"
18#include "Stroika/Foundation/Common/Concepts.h"
21
22namespace Stroika::Foundation::Time {
23 class Duration;
24}
26
27 namespace Private_ {
28
29 /**
30 * \brief checks if the given type has a .ToString () const method returning a string
31 */
32 template <typename T>
33 concept has_ToStringMethod_v = requires (T t) {
34 { t.ToString () } -> convertible_to<Characters::String>;
35 };
36 /**
37 * \brief this given type appears to be a 'pair' of some sort
38 */
39 template <typename T>
40 concept has_pair_v = requires (T t) {
41 t.first;
42 t.second;
43 };
44 /**
45 * \brief this given type appears to be a 'KeyValuePair' of some sort
46 */
47 template <typename T>
48 concept has_KeyValuePair_v = requires (T t) {
49 t.fKey;
50 t.fValue;
51 };
52 /**
53 * \brief this given type appears to be a 'CountedValue' of some sort
54 */
55 template <typename T>
56 concept has_CountedValue_v = requires (T t) {
57 t.fValue;
58 t.fCount;
59 };
60
61 template <typename T>
62 inline String num2Str_ (T t, ios_base::fmtflags flags)
63 {
64 static_assert (sizeof (t) <= sizeof (int));
65 wchar_t buf[1024];
66 switch (flags) {
67 case ios_base::oct:
68 (void)::swprintf (buf, std::size (buf), L"%o", t);
69 break;
70 case ios_base::dec:
71 (void)::swprintf (buf, std::size (buf), L"%d", t);
72 break;
73 case ios_base::hex:
74 (void)::swprintf (buf, std::size (buf), L"0x%x", t);
75 break;
76 default:
77 AssertNotReached (); // @todo support octal
78 }
79 return buf;
80 }
81 template <typename T>
82 inline String num2Strl_ (T t, ios_base::fmtflags flags)
83 {
84 wchar_t buf[1024];
85 static_assert (sizeof (t) == sizeof (long int));
86 switch (flags) {
87 case ios_base::oct:
88 (void)::swprintf (buf, std::size (buf), L"%lo", t);
89 break;
90 case ios_base::dec:
91 (void)::swprintf (buf, std::size (buf), L"%ld", t);
92 break;
93 case ios_base::hex:
94 (void)::swprintf (buf, std::size (buf), L"0x%lx", t);
95 break;
96 default:
97 AssertNotReached (); // @todo support octal
98 }
99 return buf;
100 }
101 template <typename T>
102 inline String num2Strll_ (T t, ios_base::fmtflags flags)
103 {
104 wchar_t buf[1024];
105 static_assert (sizeof (t) == sizeof (long long int));
106 switch (flags) {
107 case ios_base::oct:
108 (void)::swprintf (buf, std::size (buf), L"%llo", t);
109 break;
110 case ios_base::dec:
111 (void)::swprintf (buf, std::size (buf), L"%lld", t);
112 break;
113 case ios_base::hex:
114 (void)::swprintf (buf, std::size (buf), L"0x%llx", t);
115 break;
116 default:
117 AssertNotReached (); // @todo support octal
118 }
119 return buf;
120 }
121
122 }
123
124 /**
125 * Collect all the default ToString() implementations, templates, overloads etc, all in one namespace.
126 *
127 * Users of the Stroika library may specialize Characters::ToString(), but (???) probably should not add overloads ot the
128 * ToStringDefaults namespace --LGP 2023-11-21.
129 */
130 namespace ToStringDefaults {
131
132 // IN CPP FILE
133 String ToString (const exception_ptr& t);
134 String ToString (const exception& t);
135 String ToString (const type_info& t);
136 String ToString (const type_index& t);
137 String ToString (const thread::id& t);
138
139 template <Private_::has_ToStringMethod_v T>
140 inline String ToString (const T& t)
141 {
142 return t.ToString ();
143 }
144 template <ranges::range T>
145 String ToString (const T& t)
146 requires (not Private_::has_ToStringMethod_v<T> and not is_convertible_v<T, String>)
147 {
148 StringBuilder sb;
149 sb << "["sv;
150 bool didFirst{false};
151 for (const auto& i : t) {
152 if (didFirst) {
153 sb << ", "_k;
154 }
155 else {
156 sb << " "_k;
157 }
158 sb << Characters::ToString (i);
159 didFirst = true;
160 }
161 if (didFirst) {
162 sb << " "_k;
163 }
164 sb << "]"_k;
165 return sb;
166 }
167 template <typename T>
168 inline String ToString (const T& t)
169 requires (is_convertible_v<T, String>)
170 {
171 return static_cast<String> (t);
172 }
173 template <typename T>
174 inline String ToString ([[maybe_unused]] const T& t)
175 requires (is_convertible_v<T, tuple<>>)
176 {
177 return "{}"sv;
178 }
179 template <IToString T1>
180 inline String ToString (const atomic<T1>& t)
181 {
182 return Characters::ToString (t.load ());
183 }
184 template <typename T1>
185 String ToString (const tuple<T1>& t)
186 {
187 StringBuilder sb;
188 sb << "{"sv << Characters::ToString (get<0> (t)) << "}"sv;
189 return sb;
190 }
191 template <typename T1, typename T2>
192 String ToString (const tuple<T1, T2>& t)
193 {
194 StringBuilder sb;
195 sb << "{"sv << Characters::ToString (get<0> (t)) << ", "sv << Characters::ToString (get<1> (t)) << "}"sv;
196 return sb;
197 }
198 template <typename T1, typename T2, typename T3>
199 String ToString (const tuple<T1, T2, T3>& t)
200 {
201 StringBuilder sb;
202 sb << "{"sv;
203 sb << Characters::ToString (get<0> (t)) << ", "sv << Characters::ToString (get<1> (t)) << ", "sv << Characters::ToString (get<2> (t));
204 sb << "}"sv;
205 return sb;
206 }
207 template <typename T1, typename T2, typename T3, typename T4>
208 String ToString (const tuple<T1, T2, T3, T4>& t)
209 {
210 StringBuilder sb;
211 sb << "{"sv;
212 sb << Characters::ToString (get<0> (t)) << ", "sv << Characters::ToString (get<1> (t)) << ", "sv
213 << Characters::ToString (get<2> (t)) << ", "sv << Characters::ToString (get<3> (t));
214 sb << "}"sv;
215 return sb;
216 }
217 template <typename... TYPES>
218 String ToString (const variant<TYPES...>& v)
219 {
220 // Characters::ToString, qualified: an unqualified ToString here would see only the overloads declared above this
221 // one - so an alternative whose overload comes later (the integers) would convert to bool, and print "true"
222 return visit ([] (const auto& a) { return Characters::ToString (a); }, v);
223 }
224 template <typename T>
225 String ToString (const T& t)
226 requires (Private_::has_pair_v<T>)
227 {
228 StringBuilder sb;
229 sb << "{"sv;
230 sb << Characters::ToString (t.first) << ": "sv << Characters::ToString (t.second);
231 sb << "}"sv;
232 return sb;
233 }
234 template <Private_::has_KeyValuePair_v T>
235 String ToString (const T& t)
236 {
237 StringBuilder sb;
238 sb << "{"sv;
239 sb << Characters::ToString (t.fKey) << ": "sv << Characters::ToString (t.fValue);
240 sb << "}"sv;
241 return sb;
242 }
243 template <Private_::has_CountedValue_v T>
244 String ToString (const T& t)
245 {
246 StringBuilder sb;
247 sb << "{"sv;
248 sb << "'" << Characters::ToString (t.fValue) << "': "sv << Characters::ToString (t.fCount);
249 sb << "}"sv;
250 return sb;
251 }
252 template <typename T>
253 inline String ToString (const T& t)
254 requires (is_enum_v<T>)
255 {
256 if constexpr (not Common::IBoundedEnum<T>) {
257 return Characters::ToString (underlying_type_t<T> (t));
258 }
259 else if constexpr (Common::DefaultNames<T>{}.size () == 0) {
260 // emit as number if no EnumNames<> declared
261 return Characters::ToString (underlying_type_t<T> (t));
262 }
263 else {
264 return Common::DefaultNames<T>{}.GetName (t);
265 }
266 }
267 template <typename OPTIONS>
268 inline String ToString (const StringBuilder<OPTIONS>& sb)
269 {
270 return sb.template As<String> ();
271 }
272 template <floating_point T>
273 inline String ToString (T t)
274 {
275 return FloatConversion::ToString (t);
276 }
277 template <floating_point T>
278 inline String ToString (T t, FloatConversion::ToStringOptions o)
279 {
280 return FloatConversion::ToString (t, o);
281 }
282 template <typename T>
283 inline String ToString (const Math::CommonStatistics<T>& t)
284 {
286
287 sb << "{"sv;
288 if (t.fMin) {
289 sb << "min: " << *t.fMin;
290 }
291 if (t.fMax) {
292 sb << "max: " << *t.fMax;
293 }
294 if (t.fMean) {
295 sb << "mean: " << *t.fMean;
296 }
297 if (t.fMedian) {
298 sb << "median: " << *t.fMedian;
299 }
300 if (t.fStandardDeviation) {
301 sb << "standard-deviation: " << *t.fStandardDeviation;
302 };
303 sb << "}"sv;
304 return sb;
305 }
306 template <typename T>
307 inline String ToString (const shared_ptr<T>& pt)
308 {
309 if (pt == nullptr) {
310 return "nullptr"sv;
311 }
312 if constexpr (IToString<decltype (*pt)>) {
313 return String{Common::StdCompat::format (L"*{}", Characters::ToString (*pt))};
314 }
315 return String{Common::StdCompat::format (L"{}", static_cast<const void*> (pt.get ()))};
316 }
317 template <typename T>
318 inline String ToString (const unique_ptr<T>& pt)
319 {
320 if (pt == nullptr) {
321 return "nullptr"sv;
322 }
323 if constexpr (IToString<decltype (*pt)>) {
324 return String{Common::StdCompat::format (L"*{}", Characters::ToString (*pt))};
325 }
326 return String{Common::StdCompat::format (L"{}", static_cast<const void*> (pt.get ()))};
327 }
328 // A raw pointer prints its address, as "{}"_f prints a void* (0x...) - never what it points to, which may dangle. Not a
329 // character pointer (a C string: the is_convertible_v<T, String> overload), nor an array (taken by reference, an array
330 // does not decay, so T is not a pointer)
331 template <typename T>
332 inline String ToString (const T& t)
333 requires (is_pointer_v<T> and not is_function_v<remove_pointer_t<T>> and not is_convertible_v<T, String>)
334 {
335 return String{Common::StdCompat::format (L"{}", static_cast<const void*> (t))};
336 }
337 template <typename T>
338 inline String ToString (const optional<T>& o)
339 {
340 return o.has_value () ? Characters::ToString (*o) : "[missing]"sv;
341 }
342 template <typename FUNCTION_SIGNATURE>
343 inline String ToString (const function<FUNCTION_SIGNATURE>& f)
344 {
345 return Common::StdCompat::format (L"{}", static_cast<const void*> (f.template target<remove_cvref_t<FUNCTION_SIGNATURE>> ()));
346 }
347 inline String ToString (const chrono::duration<double>& t)
348 {
349 return Characters::ToString (t.count ()) + " seconds"sv;
350 }
352 template <typename CLOCK_T>
353 inline String ToString (const chrono::time_point<CLOCK_T, chrono::duration<double>>& t)
354 {
355 return Characters::ToString (t.time_since_epoch ().count ()) + " seconds"sv;
356 }
357 template <integral T>
358 inline String ToString (T t, ios_base::fmtflags flags)
359 {
360 using namespace Private_;
361 if constexpr (sizeof (T) < sizeof (long)) {
362 return num2Str_ (t, flags);
363 }
364 else if constexpr (sizeof (T) == sizeof (long)) {
365 return num2Strl_ (t, flags);
366 }
367 else if constexpr (sizeof (T) == sizeof (long long int)) {
368 return num2Strll_ (t, flags);
369 }
370 }
371 // A template, so nothing converts to bool to get here: an int, a pointer or a class with an operator bool must
372 // find its own overload, or not compile - never print as true/false
373 template <same_as<bool> T>
374 inline String ToString (T t)
375 {
376 // static: copying a String only counts a reference; making one from "true"sv allocates a rep, each call
377 static const String kTrue_{"true"sv};
378 static const String kFalse_{"false"sv};
379 return t ? kTrue_ : kFalse_;
380 }
381 template <signed_integral T>
382 inline String ToString (T t)
383 {
384 return Characters::ToString (t, ios_base::dec);
385 }
386 template <unsigned_integral T>
387 inline String ToString (T t)
388 requires (not same_as<T, bool>) // bool is an unsigned_integral
389 {
390 // no overwhelmingly good reason todo it this way, but this matches what we had in Stroika 2.1, and its reasonable...
391 if constexpr (sizeof (T) == 1) {
392 return Characters::ToString (t, ios_base::hex);
393 }
394 else {
395 return Characters::ToString (t, ios_base::dec);
396 }
397 }
398 inline String ToString (byte t)
399 {
400 return Characters::ToString (static_cast<unsigned char> (t), ios_base::hex);
401 }
402 inline String ToString (const filesystem::path& t)
403 {
404 return t.wstring ();
405 }
406
407 }
408
409 namespace Private_ {
410 // IToString's test - here, after the last ToStringDefaults overload: a qualified name in a template is looked up where
411 // the template is defined, so any earlier would miss the overloads declared after it
412 template <typename T>
413 struct HasToStringDefault_ {
414 static constexpr bool value = requires (T t) { ToStringDefaults::ToString (t); };
415 };
416 }
417
418 /*
419 ********************************************************************************
420 *************************** Characters::ToString *******************************
421 ********************************************************************************
422 */
423 template <typename T, typename... ARGS>
424 inline String ToString (T&& t, ARGS... args)
425 {
426 return ToStringDefaults::ToString (forward<T> (t), forward<ARGS> (args)...);
427 }
428
429 /*
430 ********************************************************************************
431 *********************** Characters::UnoverloadedToString ***********************
432 ********************************************************************************
433 */
434 template <typename T>
435 inline String UnoverloadedToString (const T& t)
436 {
437 return ToString (t);
438 }
439
440 //DEPRECATED
441 namespace Private_ {
442 template <typename T>
443 using has_ToString_t = decltype (static_cast<Characters::String> (declval<T&> ().ToString ()));
444 }
445 /*
446 * \brief Return true iff Characters::ToString (T) is well defined.
447 */
448 template <typename T>
449 [[deprecated ("Since Stroika v3.0d5 use IToString")]] constexpr inline bool has_ToString_v =
450 Common::is_detected_v<Private_::has_ToString_t, T>;
451
452}
453
454namespace Stroika::Foundation::Traversal {
455 template <typename T>
457 {
458 return Join (separator, nullopt);
459 }
460 template <typename T>
461 inline Characters::String Iterable<T>::Join (const Characters::String& separator, const optional<Characters::String>& finalSeparator) const
462 {
463 using namespace Characters;
464#if qCompilerAndStdLib_kDefaultToStringConverter_Buggy
465 function<String (T)> cvt;
466 if constexpr (same_as<T, String>) {
467 cvt = Common::Identity{};
468 }
469 else {
470 cvt = UnoverloadedToString<T>;
471 }
472 return this->Join (cvt, StringCombiner<String>{.fSeparator = separator, .fSpecialSeparatorForLastPair = finalSeparator});
473#else
474 return this->Join (kDefaultToStringConverter<String>,
475 StringCombiner<String>{.fSeparator = separator, .fSpecialSeparatorForLastPair = finalSeparator});
476#endif
477 }
478 template <typename T>
479 template <typename RESULT_T, invocable<T> CONVERT_TO_RESULT>
480 inline RESULT_T Iterable<T>::Join (const CONVERT_TO_RESULT& convertToResult, const RESULT_T& separator, const optional<RESULT_T>& finalSeparator) const
481 requires (convertible_to<invoke_result_t<CONVERT_TO_RESULT, T>, RESULT_T>)
482 {
483 return this->Join<RESULT_T> (
484 convertToResult, Characters::StringCombiner<RESULT_T>{.fSeparator = separator, .fSpecialSeparatorForLastPair = finalSeparator});
485 }
486
487}
#define AssertNotReached()
Definition Assertions.h:358
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
nonvirtual RESULT_T Join(const CONVERT_TO_RESULT &convertToResult=kDefaultToStringConverter<>, const COMBINER &combiner=Characters::kDefaultStringCombiner) const
ape the JavaScript/python 'join' function - take the parts of 'this' iterable and combine them into a...
Check if legal to call Characters::ToString(T)...
Definition ToString.h:120
this given type appears to be a 'CountedValue' of some sort
Definition ToString.inl:56
this given type appears to be a 'KeyValuePair' of some sort
Definition ToString.inl:48
checks if the given type has a .ToString () const method returning a string
Definition ToString.inl:33
this given type appears to be a 'pair' of some sort
Definition ToString.inl:40
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
String UnoverloadedToString(const T &t)
same as ToString()/1 - but without the potentially confusing multi-arg overloads (confused some templ...
Definition ToString.inl:435
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:424
StringCombiner is a simple function object used to combine two strings visually - used in Iterable<>:...
Definition String.h:1942
function object whose action is to map its argument, back to the same value it started with (identity...