Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Duration.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <cmath>
7#include <cstdio>
8
10#include "Stroika/Foundation/Characters/FloatConversion.h"
14#include "Stroika/Foundation/Linguistics/MessageUtilities.h"
15#include "Stroika/Foundation/Math/Common.h"
17
18using namespace Stroika::Foundation;
20using namespace Stroika::Foundation::Execution;
21using namespace Stroika::Foundation::Time;
22
24
25using namespace Time;
26
27// Comment this in to turn on aggressive noisy DbgTrace in this module
28//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
29
30/*
31 ********************************************************************************
32 ********************** Duration::FormatException *******************************
33 ********************************************************************************
34 */
35Duration::FormatException::FormatException ()
36 : inherited{"Invalid Duration Format"sv}
37{
38}
39
40/*
41 ********************************************************************************
42 *********************************** Duration ***********************************
43 ********************************************************************************
44 */
45namespace {
46 // sigh - not sure why so hard still -- LGP 2023-01-22 ;-)
47 constexpr wstring_view kMS_ = L"\u00b5s"sv; // u8"µs"sv
48 static_assert (kMS_.size () == 2 && kMS_[0] == 0x00B5 and kMS_[1] == 's');
49 //static_assert (sizeof (u8"µ") == 3 and u8"µ"[0] == 0xC2 and u8"µ"[1] == 0xB5); // be sure above encoding working, since appantly not well standardized (file has UTF8 BOM)
50}
51const Duration::PrettyPrintInfo Duration::kDefaultPrettyPrintInfo = {{
52 "year"sv, "years"sv, "month"sv, "months"sv, "week"sv, "weeks"sv, "day"sv, "days"sv, "hour"sv, "hours"sv, "minute"sv,
53 "minutes"sv, "second"sv, "seconds"sv, "ms"sv, "ms"sv, kMS_, kMS_, "ns"sv, "ns"sv, "ps"sv, "ps"sv,
54}};
55
56const Duration::AgePrettyPrintInfo Duration::kDefaultAgePrettyPrintInfo = {
57 {
58 "now"sv,
59 "ago"sv,
60 "from now"sv,
61 },
62 12 * 60 /*fNowThreshold*/
63};
64
66{
67 *this = *this + rhs;
68 return *this;
69}
70
71Duration& Duration::operator-= (const Duration& rhs)
72{
73 *this = *this - rhs;
74 return *this;
75}
76
77namespace {
78 string::const_iterator SkipWhitespace_ (string::const_iterator i, string::const_iterator end)
79 {
80 // GNU LIBC code (header) says that whitespace is allowed (though I've found no external docs to support this).
81 // Still - no harm in accepting this - so long as we don't ever generate it...
82 while (i != end and isspace (*i)) {
83 ++i;
84 }
85 Ensure (i <= end);
86 return i;
87 }
88 string::const_iterator FindFirstNonDigitOrDot_ (string::const_iterator i, string::const_iterator end)
89 {
90 while (i != end and (isdigit (*i) or *i == '.')) {
91 ++i;
92 }
93 Ensure (i <= end);
94 return i;
95 }
96
97 constexpr time_t kSecondsPerMinute_ = 60;
98 constexpr time_t kSecondsPerHour_ = kSecondsPerMinute_ * 60;
99 constexpr time_t kSecondsPerDay_ = kSecondsPerHour_ * 24;
100 constexpr time_t kSecondsPerWeek_ = kSecondsPerDay_ * 7;
101 constexpr time_t kSecondsPerMonth_ = kSecondsPerDay_ * 30;
102 constexpr time_t kSecondsPerYear_ = kSecondsPerDay_ * 365;
103}
104
105String Duration::PrettyPrint (const PrettyPrintInfo& prettyPrintInfo) const
106{
107 auto lingMgr = Linguistics::MessageUtilities::Manager::sThe.LookupHandler ();
108 static const String kCommaSpace_{", "sv};
109 if (empty ()) {
110 return String{};
111 }
112 /*
113 * From http://physics.nist.gov/cuu/Units/checklist.html
114 * There is a space between the numerical value and unit symbol, even when the value is used
115 * in an adjectival sense, except in the case of superscript units for plane angle.
116 */
117 static const String kSpaceBeforeUnit_{" "sv};
118
119 InternalNumericFormatType_ t = As<InternalNumericFormatType_> ();
120 bool isNeg = (t < 0);
121 InternalNumericFormatType_ timeLeft = t < 0 ? -t : t;
122 StringBuilder result;
123 if (timeLeft >= kSecondsPerYear_) {
124 unsigned int nYears = static_cast<unsigned int> (timeLeft / kSecondsPerYear_);
125 if (nYears != 0) {
126 if (not result.empty ()) {
127 result << kCommaSpace_;
128 }
129 result << nYears << kSpaceBeforeUnit_
130 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fYear, prettyPrintInfo.fLabels.fYears, static_cast<int> (nYears));
131 timeLeft -= nYears * kSecondsPerYear_;
132 }
133 }
134 if (timeLeft >= kSecondsPerMonth_) {
135 unsigned int nMonths = static_cast<unsigned int> (timeLeft / kSecondsPerMonth_);
136 if (nMonths != 0) {
137 if (not result.empty ()) {
138 result << kCommaSpace_;
139 }
140 result << "{}"_f(nMonths) << kSpaceBeforeUnit_
141 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fMonth, prettyPrintInfo.fLabels.fMonths, static_cast<int> (nMonths));
142 timeLeft -= nMonths * kSecondsPerMonth_;
143 }
144 }
145 if (timeLeft >= kSecondsPerDay_) {
146 unsigned int nDays = static_cast<unsigned int> (timeLeft / kSecondsPerDay_);
147 if (nDays != 0) {
148 if (not result.empty ()) {
149 result << kCommaSpace_;
150 }
151 result << "{}"_f(nDays) << kSpaceBeforeUnit_
152 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fDay, prettyPrintInfo.fLabels.fDays, static_cast<int> (nDays));
153 timeLeft -= nDays * kSecondsPerDay_;
154 }
155 }
156 if (timeLeft != 0) {
157 if (timeLeft >= kSecondsPerHour_) {
158 unsigned int nHours = static_cast<unsigned int> (timeLeft / kSecondsPerHour_);
159 if (nHours != 0) {
160 if (not result.empty ()) {
161 result << kCommaSpace_;
162 }
163 result << "{}"_f(nHours) << kSpaceBeforeUnit_
164 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fHour, prettyPrintInfo.fLabels.fHours, static_cast<int> (nHours));
165 timeLeft -= nHours * kSecondsPerHour_;
166 }
167 }
168 if (timeLeft >= kSecondsPerMinute_) {
169 unsigned int nMinutes = static_cast<unsigned int> (timeLeft / kSecondsPerMinute_);
170 if (nMinutes != 0) {
171 if (not result.empty ()) {
172 result << kCommaSpace_;
173 }
174 result << "{}"_f(nMinutes) << kSpaceBeforeUnit_
175 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fMinute, prettyPrintInfo.fLabels.fMinutes, static_cast<int> (nMinutes));
176 timeLeft -= nMinutes * kSecondsPerMinute_;
177 }
178 }
179 if (timeLeft > 0) {
180 int timeLeftAsInt = static_cast<int> (timeLeft);
181 if (timeLeftAsInt != 0) {
182 Assert (timeLeftAsInt > 0);
183 if (not result.empty ()) {
184 result += kCommaSpace_;
185 }
186 // Map 3.242 to printing out 3.242, but 0.234 prints out as 234 milliseconds
187 if (fabs (timeLeft - timeLeftAsInt) < 0.001) {
188 result << "{}"_f(static_cast<int> (timeLeft)) << kSpaceBeforeUnit_
189 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fSecond, prettyPrintInfo.fLabels.fSeconds, timeLeftAsInt);
190 timeLeft -= static_cast<int> (timeLeft);
191 }
192 else {
193 result << "{:.3f}"_f(timeLeft) << kSpaceBeforeUnit_ << prettyPrintInfo.fLabels.fSeconds;
194 timeLeft = 0.0;
195 }
196 }
197 }
198 if (timeLeft > 0) {
199 // DO nano, micro, milliseconds here
200
201 static const FloatConversion::ToStringOptions kFinalFloatOptions_{FloatConversion::eTrimZeros};
202
203 static constexpr bool kFirstSubSecondUnitDoDecimalPlaceImmediately_{true};
204
205 uint16_t nMilliseconds = static_cast<uint16_t> (floor (timeLeft * 1.0e3));
206 Assert (0 <= nMilliseconds and nMilliseconds < 1000);
207 // intentionally show something like 1003 us as 1003 us, not 1.003 ms
208 if (nMilliseconds > 2 or Math::NearlyEquals (timeLeft, .001) or Math::NearlyEquals (timeLeft, .002)) {
209 if (not result.empty ()) {
210 result << kCommaSpace_;
211 }
212 if (kFirstSubSecondUnitDoDecimalPlaceImmediately_) {
213 result << FloatConversion::ToString (timeLeft * 1.0e3, kFinalFloatOptions_) << kSpaceBeforeUnit_
214 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fMilliSecond, prettyPrintInfo.fLabels.fMilliSeconds, nMilliseconds);
215 timeLeft = 0;
216 }
217 else {
218 result << int (nMilliseconds) << kSpaceBeforeUnit_
219 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fMilliSecond, prettyPrintInfo.fLabels.fMilliSeconds, nMilliseconds);
220 timeLeft -= 1.0e-3 * nMilliseconds;
221 }
222 }
223 uint16_t nMicroSeconds = static_cast<uint16_t> (floor (timeLeft * 1.0e6));
224 if (nMicroSeconds > 0) {
225 if (not result.empty ()) {
226 result << kCommaSpace_;
227 }
228 if (kFirstSubSecondUnitDoDecimalPlaceImmediately_) {
229 result << FloatConversion::ToString (timeLeft * 1.0e6, kFinalFloatOptions_) << kSpaceBeforeUnit_
230 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fMicroSecond, prettyPrintInfo.fLabels.fMicroSeconds, nMicroSeconds);
231 timeLeft = 0;
232 }
233 else {
234 result << int (nMicroSeconds) << kSpaceBeforeUnit_
235 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fMicroSecond, prettyPrintInfo.fLabels.fMicroSeconds, nMicroSeconds);
236 timeLeft -= 1.0e-6 * nMicroSeconds;
237 }
238 }
239 uint16_t nNanoSeconds = static_cast<uint16_t> (floor (timeLeft * 1.0e9));
240 if (nNanoSeconds > 0) {
241 if (not result.empty ()) {
242 result << kCommaSpace_;
243 }
244 if (kFirstSubSecondUnitDoDecimalPlaceImmediately_) {
245 result << FloatConversion::ToString (timeLeft * 1.0e9, kFinalFloatOptions_) << kSpaceBeforeUnit_
246 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fNanoSecond, prettyPrintInfo.fLabels.fNanoSeconds, nNanoSeconds);
247 timeLeft = 0;
248 }
249 else {
250 result << int (nMicroSeconds) << kSpaceBeforeUnit_
251 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fNanoSecond, prettyPrintInfo.fLabels.fNanoSecond, nNanoSeconds);
252 timeLeft -= 1.0e-9 * nNanoSeconds;
253 }
254 }
255 Duration::InternalNumericFormatType_ nPicoSeconds = timeLeft * 1.0e12;
256 if (nPicoSeconds > 1.0e-5) {
257 if (not result.empty ()) {
258 result << kCommaSpace_;
259 }
260 Duration::InternalNumericFormatType_ extraBits = nPicoSeconds - floor (nPicoSeconds);
261 if (extraBits > 1.0e-2) {
262 result << nPicoSeconds << kSpaceBeforeUnit_
263 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fPicoSecond, prettyPrintInfo.fLabels.fPicoSeconds, 2);
264 }
265 else {
266 result << int (nPicoSeconds) << kSpaceBeforeUnit_
267 << lingMgr->PluralizeNoun (prettyPrintInfo.fLabels.fPicoSecond, prettyPrintInfo.fLabels.fPicoSeconds, int (nPicoSeconds));
268 }
269 }
270 }
271 }
272 if (result.empty ()) {
273 result << "0"sv << kSpaceBeforeUnit_ << prettyPrintInfo.fLabels.fSeconds;
274 }
275 if (isNeg) {
276 static const String kNeg_{"-"sv};
277 result = kNeg_ + result;
278 }
279 return result;
280}
281
282String Duration::PrettyPrintAge (const AgePrettyPrintInfo& agePrettyPrintInfo, const PrettyPrintInfo& prettyPrintInfo) const
283{
284 InternalNumericFormatType_ t = As<InternalNumericFormatType_> ();
285 bool isNeg = (t < 0);
286 InternalNumericFormatType_ absT = isNeg ? -t : t;
287 if (absT < agePrettyPrintInfo.fNowThreshold) {
288 return agePrettyPrintInfo.fLabels.fNow;
289 }
290
291 String suffix = isNeg ? agePrettyPrintInfo.fLabels.fAgo : agePrettyPrintInfo.fLabels.fFromNow;
292
293 auto fmtDate = [suffix] (int timeInSelectedUnit, const String& singularUnit, const String& pluralUnit) -> String {
294 String label = Linguistics::MessageUtilities::Manager::sThe.PluralizeNoun (singularUnit, pluralUnit, timeInSelectedUnit);
295 return "{} {} {}"_f(timeInSelectedUnit, label, suffix);
296 };
297
298 constexpr InternalNumericFormatType_ kShowAsMinutesIfLess_ = 55 * kSecondsPerMinute_;
299 constexpr InternalNumericFormatType_ kShowHoursIfLess_ = 23 * kSecondsPerHour_;
300 constexpr InternalNumericFormatType_ kShowDaysIfLess_ = 14 * kSecondsPerDay_;
301 constexpr InternalNumericFormatType_ kShowWeeksIfLess_ = 59 * kSecondsPerDay_;
302 constexpr InternalNumericFormatType_ kShowMonthsIfLess_ = 11 * kSecondsPerMonth_;
303
304 if (absT < kShowAsMinutesIfLess_) {
305 return fmtDate (Math::Round<int> (absT / kSecondsPerMinute_), prettyPrintInfo.fLabels.fMinute, prettyPrintInfo.fLabels.fMinutes);
306 }
307 if (absT < kShowHoursIfLess_) {
308 return fmtDate (Math::Round<int> (absT / kSecondsPerHour_), prettyPrintInfo.fLabels.fHour, prettyPrintInfo.fLabels.fHours);
309 }
310 if (absT < kShowDaysIfLess_ and not Math::NearlyEquals (absT, static_cast<InternalNumericFormatType_> (kSecondsPerWeek_), 1.0)) {
311 return fmtDate (Math::Round<int> (absT / kSecondsPerDay_), prettyPrintInfo.fLabels.fDay, prettyPrintInfo.fLabels.fDays);
312 }
313 if (absT < kShowWeeksIfLess_ and not Math::NearlyEquals (absT, static_cast<InternalNumericFormatType_> (kSecondsPerMonth_), 1.0)) {
314 return fmtDate (Math::Round<int> (absT / kSecondsPerWeek_), prettyPrintInfo.fLabels.fWeek, prettyPrintInfo.fLabels.fWeeks);
315 }
316 if (absT < kShowMonthsIfLess_) {
317 return fmtDate (Math::Round<int> (absT / kSecondsPerMonth_), prettyPrintInfo.fLabels.fMonth, prettyPrintInfo.fLabels.fMonths);
318 }
319 return fmtDate (Math::Round<int> (absT / kSecondsPerYear_), prettyPrintInfo.fLabels.fYear, prettyPrintInfo.fLabels.fYears);
320}
321
323{
324 String tmp = As<String> ();
325 if (tmp.empty ()) {
326 return *this;
327 }
328 if (tmp[0] == '-') {
329 return Duration{tmp.substr (1)};
330 }
331 else {
332 return Duration{"-"sv + tmp};
333 }
334}
335
336Duration::InternalNumericFormatType_ Duration::ParseTime_ (const string& s)
337{
338#if USE_NOISY_TRACE_IN_THIS_MODULE_
339 Debug::TraceContextBumper ctx{"Duration::ParseTime_", "s = {}"_f, s};
340#endif
341 if (s.empty ()) {
342 return kValueWhenEmptyRenderedAsNumber_;
343 }
344 InternalNumericFormatType_ curVal = 0;
345 bool isNeg = false;
346 // compute and throw if bad...
347 string::const_iterator i = SkipWhitespace_ (s.begin (), s.end ());
348 if (*i == '-') {
349 isNeg = true;
350 i = SkipWhitespace_ (i + 1, s.end ());
351 }
352 if (*i == 'P') [[likely]] {
353 i = SkipWhitespace_ (i + 1, s.end ());
354 }
355 else {
356 Throw (FormatException::kThe);
357 }
358 bool timePart = false;
359 while (i != s.end ()) {
360 if (*i == 'T') {
361 timePart = true;
362 i = SkipWhitespace_ (i + 1, s.end ());
363 continue;
364 }
365 string::const_iterator firstDigitI = i;
366 string::const_iterator lastDigitI = FindFirstNonDigitOrDot_ (i, s.end ());
367 if (lastDigitI == s.end ()) [[unlikely]] {
368 Throw (FormatException::kThe);
369 }
370 if (firstDigitI == lastDigitI) [[unlikely]] {
371 Throw (FormatException::kThe);
372 }
373 /*
374 * According to http://en.wikipedia.org/wiki/ISO_8601
375 * "The smallest value used may also have a decimal fraction, as in "P0.5Y" to indicate
376 * half a year. This decimal fraction may be specified with either a comma or a full stop,
377 * as in "P0,5Y" or "P0.5Y"."
378 *
379 * @todo See todo in header: the first/last digit range could use '.' or ',' and I'm not sure atof is as flexible
380 * test/verify!!!
381 */
382 InternalNumericFormatType_ n = ::atof (string{firstDigitI, lastDigitI}.c_str ());
383 switch (*lastDigitI) {
384 case 'Y':
385 curVal += n * kSecondsPerYear_;
386 break;
387 case 'M':
388 curVal += n * (timePart ? kSecondsPerMinute_ : kSecondsPerMonth_);
389 break;
390 case 'W':
391 curVal += n * kSecondsPerWeek_;
392 break;
393 case 'D':
394 curVal += n * kSecondsPerDay_;
395 break;
396 case 'H':
397 curVal += n * kSecondsPerHour_;
398 break;
399 case 'S':
400 curVal += n;
401 break;
402 }
403 i = SkipWhitespace_ (lastDigitI + 1, s.end ());
404 }
405 return isNeg ? -curVal : curVal;
406}
407
408String Duration::UnParseTime_ (InternalNumericFormatType_ t, FloatConversion::Precision p)
409{
410#if USE_NOISY_TRACE_IN_THIS_MODULE_
411 Debug::TraceContextBumper ctx{"Duration::UnParseTime_", "t = {:e}, p={}"_f, t, p};
412#endif
413 bool isNeg = (t < 0);
414 InternalNumericFormatType_ timeLeft = t < 0 ? -t : t;
415 StringBuilder result;
416 if (isNeg) {
417 result += "-"sv;
418 }
419 result += "P"sv;
420 if (timeLeft >= kSecondsPerYear_) {
421 InternalNumericFormatType_ nYears = trunc (timeLeft / kSecondsPerYear_);
422 Assert (nYears > 0.0);
423 if (nYears > 0.0) {
424 char buf[10 * 1024];
425 (void)::snprintf (buf, sizeof (buf), "%.0LfY", static_cast<long double> (nYears));
426 result += buf;
427 timeLeft -= nYears * kSecondsPerYear_;
428 if (isinf (timeLeft) or timeLeft < 0) {
429 // some date numbers are so large, we cannot compute a number of days, weeks etc
430 // Also, for reasons which elude me (e.g. 32 bit gcc builds) this can go negative.
431 // Not strictly a bug (I don't think). Just roundoff.
432 timeLeft = 0.0;
433 }
434 }
435 }
436 Assert (0.0 <= timeLeft and timeLeft < kSecondsPerYear_);
437 if (timeLeft >= kSecondsPerMonth_) {
438 unsigned int nMonths = static_cast<unsigned int> (timeLeft / kSecondsPerMonth_);
439 if (nMonths != 0) {
440 char buf[1024];
441 (void)::snprintf (buf, sizeof (buf), "%dM", nMonths);
442 result += buf;
443 timeLeft -= nMonths * kSecondsPerMonth_;
444 }
445 }
446 Assert (0.0 <= timeLeft and timeLeft < kSecondsPerMonth_);
447 if (timeLeft >= kSecondsPerDay_) {
448 unsigned int nDays = static_cast<unsigned int> (timeLeft / kSecondsPerDay_);
449 if (nDays != 0) {
450 char buf[1024];
451 (void)::snprintf (buf, sizeof (buf), "%dD", nDays);
452 result += buf;
453 timeLeft -= nDays * kSecondsPerDay_;
454 }
455 }
456 Assert (0.0 <= timeLeft and timeLeft < kSecondsPerDay_);
457 if (timeLeft > 0) {
458 result += "T"sv;
459 if (timeLeft >= kSecondsPerHour_) {
460 unsigned int nHours = static_cast<unsigned int> (timeLeft / kSecondsPerHour_);
461 if (nHours != 0) {
462 char buf[1024];
463 (void)::snprintf (buf, sizeof (buf), "%dH", nHours);
464 result += buf;
465 timeLeft -= nHours * kSecondsPerHour_;
466 }
467 }
468 Assert (0.0 <= timeLeft and timeLeft < kSecondsPerHour_);
469 if (timeLeft >= kSecondsPerMinute_) {
470 unsigned int nMinutes = static_cast<unsigned int> (timeLeft / kSecondsPerMinute_);
471 if (nMinutes != 0) {
472 char buf[1024];
473 (void)::snprintf (buf, sizeof (buf), "%dM", nMinutes);
474 result += buf;
475 timeLeft -= nMinutes * kSecondsPerMinute_;
476 }
477 }
478 Assert (0.0 <= timeLeft and timeLeft < kSecondsPerMinute_);
479 if (timeLeft > 0.0) {
480 result += FloatConversion::ToString (timeLeft, p);
481 result += "S"sv;
482 }
483 }
484 if (result.length () == 1) {
485 result += "T0S"sv;
486 }
487 return result;
488}
489
490/*
491 ********************************************************************************
492 ******************************** Math::Abs *************************************
493 ********************************************************************************
494 */
495namespace Stroika::Foundation::Math {
497 {
498 return (v.count () < 0) ? -v : v;
499 }
500}
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual size_t length() const noexcept
number of characters, not bytes or code-points
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual String substr(size_t from, size_t count=npos) const
Definition String.inl:1086
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
nonvirtual Duration & operator+=(const Duration &rhs)
Definition Duration.cpp:65
nonvirtual Characters::String PrettyPrintAge(const AgePrettyPrintInfo &agePrettyPrintInfo=kDefaultAgePrettyPrintInfo, const PrettyPrintInfo &prettyPrintInfo=kDefaultPrettyPrintInfo) const
Definition Duration.cpp:282
nonvirtual Characters::String PrettyPrint(const PrettyPrintInfo &prettyPrintInfo=kDefaultPrettyPrintInfo) const
Definition Duration.cpp:105
nonvirtual Duration operator-() const
Definition Duration.cpp:322
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43