DynExp
Highly flexible laboratory automation for dynamically changing experiments.
Util.cpp
Go to the documentation of this file.
1 // This file is part of DynExp.
2 
3 #include "stdafx.h"
4 #include "Util.h"
5 
6 namespace Util
7 {
8  std::unique_lock<ILockable::MutexType> ILockable::AcquireLock(const std::chrono::milliseconds Timeout) const
9  {
10  using namespace std::chrono_literals;
11 
12  // In order to compensate for spurious failures by retrying
13  // (see https://en.cppreference.com/w/cpp/thread/timed_mutex/try_lock_for)
14  constexpr int NumTries = 2;
15 
16  if (Timeout == 0ms)
17  return std::unique_lock<MutexType>(LockMutex);
18  else
19  {
20  std::unique_lock<MutexType> lock(LockMutex, std::defer_lock);
21 
22  for (auto i = NumTries; i > 0; --i)
23  if (lock.try_lock_for(Timeout / NumTries))
24  return lock;
25 
26  throw TimeoutException("Timeout occurred while trying to lock a mutex.");
27  }
28  }
29 
31  {
32  Notify();
33 
34  // See OneToOneNotifier::Wait().
35  while (!MutexCanBeDestroyed)
36  std::this_thread::yield();
37  }
38 
39  bool OneToOneNotifier::Wait(const std::chrono::milliseconds Timeout)
40  {
41  bool TimeoutOccurred = false;
42 
43  {
44  std::unique_lock<decltype(Mutex)> Lock(Mutex);
45 
46  if (SomeoneIsWaiting)
48  "There is already a waiting thread. Only one thread can await this notifier.");
49  SomeoneIsWaiting = true;
50  MutexCanBeDestroyed = false;
51 
52  if (Timeout.count())
53  TimeoutOccurred = !ConditionVariable.wait_for(Lock, Timeout, [this]() { return EventOccurred; });
54  else
55  ConditionVariable.wait(Lock, [this]() { return EventOccurred; });
56 
57  // Reset the notifier
58  EventOccurred = false;
59  SomeoneIsWaiting = false;
60  }
61 
62  // Now, OneToOneNotifier's destructor (if it was called while waiting) is allowed to end which
63  // leads to Mutex being destroyed. There is no unique_lock locking Mutex anymore at this point.
64  MutexCanBeDestroyed = true;
65 
66  return TimeoutOccurred;
67  }
68 
70  {
71  {
72  std::unique_lock<decltype(Mutex)> Lock(Mutex);
73  EventOccurred = true;
74  }
75 
76  ConditionVariable.notify_one();
77  }
78 
80  {
81  std::unique_lock<decltype(Mutex)> Lock(Mutex);
82  EventOccurred = false;
83  }
84 
86  : DataSize(Other.DataSize)
87  {
88  if (Other.DataPtr)
89  {
90  DataPtr = std::make_unique<DataType>(DataSize);
91  std::memcpy(DataPtr.get(), Other.DataPtr.get(), DataSize);
92  }
93  }
94 
96  : DataPtr(std::move(Other.DataPtr)), DataSize(Other.DataSize)
97  {
98  Other.Reset();
99  }
100 
102  {
103  Reserve(Other.DataSize);
104  std::memcpy(DataPtr.get(), Other.DataPtr.get(), DataSize);
105 
106  return *this;
107  }
108 
110  {
111  DataSize = Other.DataSize;
112  DataPtr = std::move(Other.DataPtr);
113  Other.Reset();
114 
115  return *this;
116  }
117 
118  void BlobDataType::Reserve(size_t Size)
119  {
120  if (DataSize == Size)
121  return;
122 
123  if (!Size)
124  DataPtr.reset();
125  else
126  DataPtr = std::make_unique<DataType>(Size);
127 
128  DataSize = Size;
129  }
130 
131  void BlobDataType::Assign(size_t Size, const DataType Data)
132  {
133  Reserve(Size);
134  std::memcpy(DataPtr.get(), Data, Size);
135  }
136 
138  {
139  DataPtr.reset();
140  DataSize = 0;
141  }
142 
143  BlobDataType::DataPtrType::element_type* BlobDataType::Release() noexcept
144  {
145  DataSize = 0;
146  return DataPtr.release();
147  }
148 
149  std::strong_ordering operator<=>(const VersionType& lhs, const VersionType& rhs)
150  {
151  if (lhs.Major == rhs.Major && lhs.Minor == rhs.Minor && lhs.Patch == rhs.Patch)
152  return std::strong_ordering::equal;
153 
154  if (lhs.Major > rhs.Major ||
155  (lhs.Major == rhs.Major && lhs.Minor > rhs.Minor) ||
156  (lhs.Major == rhs.Major && lhs.Minor == rhs.Minor && lhs.Patch > rhs.Patch))
157  return std::strong_ordering::greater;
158 
159  return std::strong_ordering::less;
160  }
161 
162  VersionType VersionFromString(std::string_view Str)
163  {
164  VersionType Version;
165 
166  static const std::regex VersionRegex("(\\d+)\\.(\\d+)(?:\\.(\\d+))?");
167  std::cmatch Results;
168  std::regex_search(Str.data(), Results, VersionRegex);
169  if (Results.length() <= 1 || Results.length() > 5) // Matching a suffix (like "-beta") increases Results' length by one.
170  throw Util::InvalidDataException("Str does not contain a valid version number.");
171 
172  // Results[0] contains entire match, so ignore that.
173  for (size_t i = 1; i < Results.size(); ++i)
174  {
175  if (!Results[i].matched)
176  continue;
177 
178  switch (i)
179  {
180  case 1: Version.Major = Util::StrToT<decltype(VersionType::Major)>(Results[i].str()); break;
181  case 2: Version.Minor = Util::StrToT<decltype(VersionType::Minor)>(Results[i].str()); break;
182  case 3: Version.Patch = Util::StrToT<decltype(VersionType::Patch)>(Results[i].str());
183  }
184  }
185 
186  return Version;
187  }
188 
189  std::string ExceptionToStr(const std::exception_ptr ExceptionPtr)
190  {
191  if (!ExceptionPtr)
192  return "";
193 
194  try
195  {
196  std::rethrow_exception(ExceptionPtr);
197  }
198  catch (const std::exception& e)
199  {
200  return e.what();
201  }
202  catch (...)
203  {
204  return "Unknown exception.";
205  }
206  }
207 
208  std::string ToLower(std::string_view Str)
209  {
210  std::string LowerStr;
211  LowerStr.resize(Str.size());
212 
213  std::transform(Str.cbegin(), Str.cend(), LowerStr.begin(),
214  [](unsigned char c) { return std::tolower(c); }
215  );
216 
217  return LowerStr;
218  }
219 
220  std::vector<std::complex<double>> FFT(const std::vector<std::complex<double>>& Data, bool InverseTransform)
221  {
222  if (Data.size() > std::numeric_limits<size_t>::max() / 2)
223  throw OverflowException("Size of Data vector must not exceed half the capacity of size_t.");
224 
225  std::vector<double> RawData;
226  RawData.resize(2 * Data.size());
227  for (size_t i = 0; i < Data.size(); ++i)
228  {
229  RawData[2 * i] = Data[i].real();
230  RawData[2 * i + 1] = Data[i].imag();
231  }
232 
233  gsl_fft_complex_workspace* Workspace = gsl_fft_complex_workspace_alloc(Data.size());
234  gsl_fft_complex_wavetable* Wavetable = gsl_fft_complex_wavetable_alloc(Data.size());
235 
236  try
237  {
238  if (!Workspace || !Wavetable)
239  throw NotAvailableException("Could not reserve memory for FFT.", ErrorType::Error);
240 
241  if (gsl_fft_complex_transform(RawData.data(), 1, Data.size(), Wavetable, Workspace,
242  InverseTransform ? gsl_fft_direction::gsl_fft_backward : gsl_fft_direction::gsl_fft_forward))
243  throw InvalidDataException("The FFT failed for an unknown reason.");
244 
245  gsl_fft_complex_wavetable_free(Wavetable);
246  gsl_fft_complex_workspace_free(Workspace);
247 
248  std::vector<std::complex<double>> Result;
249  Result.resize(Data.size());
250  for (size_t i = 0; i < Result.size(); ++i)
251  Result[i] = { RawData[2 * i], RawData[2 * i + 1] };
252 
253  return Result;
254  }
255  catch (...)
256  {
257  if (Wavetable)
258  gsl_fft_complex_wavetable_free(Wavetable);
259  if (Workspace)
260  gsl_fft_complex_workspace_free(Workspace);
261 
262  throw;
263  }
264  }
265 
266  Warning::Warning(Warning&& Other) noexcept : Data(std::make_unique<WarningData>())
267  {
268  auto other_lock = Other.AcquireLock();
269 
270  Data.swap(Other.Data);
271  }
272 
274  {
275  auto lock = AcquireLock();
276 
277  Data = std::make_unique<WarningData>();
278  }
279 
281  {
282  auto lock = AcquireLock();
283 
284  Data = std::make_unique<WarningData>(e.what(), e.ErrorCode, e.Line, e.Function, e.File);
285  return *this;
286  }
287 
288  Warning& Warning::operator=(Warning&& Other) noexcept
289  {
290  auto other_lock = Other.AcquireLock();
291  auto lock = AcquireLock();
292 
293  Data.swap(Other.Data);
294  return *this;
295  }
296 
298  {
299  auto lock = AcquireLock();
300 
301  return *Data;
302  }
303 
305  {
306  LogFile.exceptions(std::ofstream::failbit | std::ofstream::badbit);
307  }
308 
309  void EventLogger::Log(const std::string& Message, const ErrorType Type,
310  const size_t Line, const std::string& Function, const std::string& File, const int ErrorCode
312  , const std::stacktrace& Trace
313 #endif // DYNEXP_HAS_STACKTRACE
314  ) noexcept
315  {
316  try
317  {
318  auto Filename = FilenameFromPath(File);
319 
320  auto lock = AcquireLock(LogOperationTimeout);
321 
322  LogEntry Entry(FormatLog(Message, Line, Function, Filename, ErrorCode, false), Type, std::chrono::system_clock::now());
323  LogEntries.emplace_back(std::move(Entry));
324 
325  if (!IsOpenUnsafe())
326  return;
327 
328  LogFile << FormatLogHTML(Message, Type, Line, Function, Filename, ErrorCode
330  , Trace
331 #endif // DYNEXP_HAS_STACKTRACE
332  );
333 
334  LogFile.flush();
335  }
336  catch (...)
337  {
338  // Swallow every exception (refer to function declaration in Util.h)
339  }
340  }
341 
342  void EventLogger::Log(const Exception& E) noexcept
343  {
344  Log(E.what(), E.Type, E.Line, E.Function, E.File, E.ErrorCode
346  , E.GetStackTrace()
347 #endif // DYNEXP_HAS_STACKTRACE
348  );
349  }
350 
351  void EventLogger::Log(const Warning& W) noexcept
352  {
353  try
354  {
355  auto Data = W.Get();
356 
357  Log(Data.Description, ErrorType::Warning, Data.Line, Data.Function, Data.File, Data.ErrorCode);
358  }
359  catch (...)
360  {
361  // Swallow every exception (refer to function declaration in Util.h)
362  }
363  }
364 
365  std::string EventLogger::FormatLog(const std::string& Message, const size_t Line,
366  const std::string& Function, const std::string& Filename, const int ErrorCode, const bool PrefixMessage)
367  {
368  std::stringstream stream;
369 
370  if (!Filename.empty() || !Function.empty())
371  {
372  stream << "(";
373  if (!Filename.empty())
374  {
375  stream << Filename << ((!Line && !Function.empty()) ? " " : "");
376  if (Line)
377  stream << ":" << ToStr(Line) << (!Function.empty() ? " " : "");
378  }
379  if (!Function.empty())
380  stream << "in " << Function << "()";
381  stream << ")" << (PrefixMessage ? "" : ": ");
382  }
383  stream << (PrefixMessage ? ": " : "") << Message;
384  if (ErrorCode)
385  stream << " (Code " << ToStr(ErrorCode) << ")";
386 
387  return stream.str();
388  }
389 
390  std::string EventLogger::FormatLogHTML(const std::string& Message, const ErrorType Type,
391  const size_t Line, const std::string& Function, const std::string& Filename, const int ErrorCode
393  , const std::stacktrace& Trace
394 #endif // DYNEXP_HAS_STACKTRACE
395  )
396  {
397  auto Label = Exception::GetErrorLabel(Type);
398  auto ColorString = Exception::GetErrorLabelColor(Type);
399 
400  std::stringstream stream;
401 
402  stream << "\n" <<
403 #ifdef DYNEXP_HAS_STACKTRACE
404  (Trace.empty() ?
405 #endif // DYNEXP_HAS_STACKTRACE
406  "<div class=\"entry_no_details\">"
407 #ifdef DYNEXP_HAS_STACKTRACE
408  : "<details><summary class=\"entry_details\">")
409 #endif // DYNEXP_HAS_STACKTRACE
410  << "<span class=\"entry_time\"><span class=\"time\">" << CurrentTimeAndDateString() << "</span></span>"
411  << "<span class=\"entry_label\"><span class=\"label\" style=\"color:" << ColorString << "\">" << Label << "</span></span>"
412  << "<span class=\"entry_text\">";
413  if (!Filename.empty() || !Function.empty())
414  {
415  stream << "<span class=\"origin\">(";
416  if (!Filename.empty())
417  {
418  stream << Filename;
419  if (Line)
420  stream << ":" << Line << (!Function.empty() ? " " : "");
421  }
422  if (!Function.empty())
423  stream << "in " << QString::fromStdString(Function).toHtmlEscaped().toStdString();
424  stream << ")</span><br>";
425  }
426  stream << "<span class=\"msg\">" << QString::fromStdString(Message).toHtmlEscaped().toStdString() << "</span>";
427  if (ErrorCode)
428  stream << " <span class=\"errno\">(Code " << ErrorCode << ")</span>";
429  stream << "\n</span>";
430 
431 #ifdef DYNEXP_HAS_STACKTRACE
432  if (!Trace.empty())
433  {
434  stream << "\n</summary><div class=\"trace\"><ol>\n";
435  for (const auto& entry : Trace)
436  stream << "<li>" << QString::fromStdString(std::to_string(entry)).toHtmlEscaped().toStdString() << "</li>\n";
437  stream << "</ol></div></details>";
438  }
439  else
440 #endif // DYNEXP_HAS_STACKTRACE
441  stream << "\n</div>";
442 
443  return stream.str();
444  }
445 
446  void EventLogger::OpenLogFile(std::string Filename)
447  {
448  auto lock = AcquireLock(LogOperationTimeout);
449 
450  ClearLogUnsafe();
452 
453  LogFile.open(Filename, std::ios_base::out | std::ios_base::trunc);
454  this->Filename = Filename;
455 
456  LogFile << "<!DOCTYPE html>\n"
457  << "<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<style>\n"
458  << "body {background-color:white; font-family:sans-serif}\n"
459  << ".version {font-style:italic}\n"
460  << ".trace {margin-left: 18em}\n"
461  << "summary::marker {content:none}\n"
462  << "summary::before {content:\"+\"; font-weight:bold; float:left; position:absolute; left:.9em}\n"
463  << "details[open] summary::before {content:\"-\"}\n"
464  << ".entry_no_details {margin-left:1.4em; margin-block:.2em}\n"
465  << ".entry_details {margin-left:1.4em; list-style-position:outside}\n"
466  << ".entry_label, .entry_text {display:table-cell; padding-right:1em; vertical-align:top; min-width:6em}\n"
467  << ".entry_time {display:table-cell; padding-right:1em; vertical-align:top; min-width:10em}\n"
468  << "div.entry_no_details:hover, summary.entry_details:hover {background-color:lightgray}\n"
469  << ".label {font-weight:bold}\n"
470  << ".origin {font-family:monospace}\n"
471  << ".errno {font-style:italic}\n"
472  << ".trace ol {font-family:monospace; margin-block-start:.2em; margin-left:2em}\n"
473  << ".end {font-style:italic}\n"
474  << "</style>\n<title>DynExp Logfile</title>\n</head>\n<body>\n"
475  << "<h1>DynExp Logfile</h1>\n"
476 #ifdef DYNEXP_DEBUG
477  << "<p class=\"version\">(" << DynExp::DynExpVersion << "-Debug)</p>\n"
478 #else
479  << "<p class=\"version\">(" << DynExp::DynExpVersion << "-Release)</p>\n"
480 #endif // DYNEXP_DEBUG
481  << "<div class=\"entries\">";
482 
483  LogFile.flush();
484  }
485 
486  std::vector<LogEntry> EventLogger::GetLog(size_t FirstElement) const
487  {
488  auto lock = AcquireLock(LogOperationTimeout);
489 
490  if (FirstElement >= LogEntries.size())
491  throw Util::OutOfRangeException("FirstElement exceeds number of log entries.");
492 
493  return { LogEntries.cbegin() + FirstElement, LogEntries.cend() };
494  }
495 
497  {
498  if (IsOpenUnsafe())
499  {
500  if (LogFile.tellp())
501  LogFile << "\n</div>\n<p class=\"end\">*** Logfile end.</p>\n</body>\n</html>";
502  LogFile.flush();
503  LogFile.close();
504  }
505 
506  Filename = "";
507  }
508 
510  {
511  static EventLogger EventLog;
512 
513  return EventLog;
514  }
515 }
Provides general utilities within DynExp's Util namespace.
Data type which manages a binary large object. The reserved memory is freed upon destruction.
Definition: Util.h:517
void Reserve(size_t Size)
Reserves Size bytes of memory freeing any previously reserved memory.
Definition: Util.cpp:118
size_t DataSize
Size of the stored data in bytes.
Definition: Util.h:541
unsigned char[] DataType
Type of the buffer's data.
Definition: Util.h:519
DataPtrType::element_type * Release() noexcept
Releases ownership of the stored buffer returning a pointer to it and leaving this instance empty.
Definition: Util.cpp:143
DataPtrType DataPtr
Pointer to the buffer.
Definition: Util.h:540
auto Size() const noexcept
Returns the size of the stored data in bytes.
Definition: Util.h:537
void Reset()
Frees any reserved memory.
Definition: Util.cpp:137
BlobDataType()=default
Constructs an empty object.
void Assign(size_t Size, const DataType Data)
Copies Size bytes from Data to the buffer freeing any previously reserved memory.
Definition: Util.cpp:131
BlobDataType & operator=(const BlobDataType &Other)
Copy-assigns data from Other.
Definition: Util.cpp:101
Logs events like errors and writes them immediately to a HTML file in a human-readable format....
Definition: Util.h:1061
bool IsOpenUnsafe() const
Definition: Util.h:1196
static constexpr auto LogOperationTimeout
Internal timeout for locking the mutex which synchronizes the calls to member function calls....
Definition: Util.h:1205
void Log(const std::string &Message, const ErrorType Type=ErrorType::Info, const size_t Line=0, const std::string &Function="", const std::string &File="", const int ErrorCode=0, const std::stacktrace &Trace={}) noexcept
Logs an event from information specified manually.
Definition: Util.cpp:309
void CloseLogFileUnsafe()
Definition: Util.cpp:496
std::ofstream LogFile
Stream object to write to the log file on disk.
Definition: Util.h:1207
std::string Filename
Filename and path to the log file on disk.
Definition: Util.h:1208
static std::string FormatLogHTML(const std::string &Message, const ErrorType Type=ErrorType::Info, const size_t Line=0, const std::string &Function="", const std::string &Filename="", const int ErrorCode=0, const std::stacktrace &Trace={})
Formats a log entry as HTML code to be displayed in a web browser.
Definition: Util.cpp:390
EventLogger()
Constructs the event logger without opening a log file on disk. Events are only stored in the interna...
Definition: Util.cpp:304
static std::string FormatLog(const std::string &Message, const size_t Line=0, const std::string &Function="", const std::string &Filename="", const int ErrorCode=0, const bool PrefixMessage=true)
Formats a log entry as plain text to be displayed within DynExp.
Definition: Util.cpp:365
std::vector< LogEntry > LogEntries
Internally stored log entries.
Definition: Util.h:1210
std::vector< LogEntry > GetLog(size_t FirstElement=0) const
Returns the internal event log starting from the n-th stored element.
Definition: Util.cpp:486
void ClearLogUnsafe()
Definition: Util.h:1198
void OpenLogFile(std::string Filename)
Opens the HTML log file on disk. If it already exists, the file gets overwritten.
Definition: Util.cpp:446
DynExp exceptions are derived from this class. It contains basic information about the cause of the e...
Definition: Exception.h:51
const std::string File
Source code file where the exception occurred.
Definition: Exception.h:109
const size_t Line
Line in source code where the exception occurred.
Definition: Exception.h:107
constexpr const char * GetErrorLabel() const
Definition: Exception.h:98
const std::string Function
Function in source code where the exception occurred
Definition: Exception.h:108
const int ErrorCode
DynExp error code from DynExpErrorCodes::DynExpErrorCodes
Definition: Exception.h:106
constexpr const char * GetErrorLabelColor() const
Definition: Exception.h:99
LockType AcquireLock(const std::chrono::milliseconds Timeout=DefaultTimeout) const
Locks the internal mutex. Blocks until the mutex is locked or until the timeout duration is exceeded.
Definition: Util.cpp:8
MutexType LockMutex
Internal mutex used for locking.
Definition: Util.h:82
Thrown when a function call is not allowed to a specific thread in a multi-threading context.
Definition: Exception.h:175
Data to operate on is invalid for a specific purpose. This indicates a corrupted data structure or fu...
Definition: Exception.h:163
Thrown when some operation or feature is temporarily or permanently not available.
Definition: Exception.h:286
bool Wait(const std::chrono::milliseconds Timeout=std::chrono::milliseconds(0))
Makes current thread wait until it is notified or until given timeout duration is exceeded....
Definition: Util.cpp:39
void Notify()
Set notification to stop waiting (sets EventOccurred to true).
Definition: Util.cpp:69
void Ignore()
Ignore last notification (sets EventOccurred to false).
Definition: Util.cpp:79
Thrown when an argument passed to a function exceeds the valid range.
Definition: Exception.h:211
Thrown when a numeric operation would result in an overflow (e.g. due to incompatible data types)
Definition: Exception.h:199
Thrown when an operation timed out before it could be completed, especially used for locking shared d...
Definition: Exception.h:261
Class to store information about warnings in a thread-safe manner (deriving from ILockable)....
Definition: Util.h:966
void Reset()
Clears the warning data.
Definition: Util.cpp:273
WarningData Get() const
Returns a copy of the warning data.
Definition: Util.cpp:297
Warning()
Constructs an empty Warning.
Definition: Util.h:1000
Warning & operator=(const Exception &e)
Retrives the warning data from an exception e derived from Exception.
Definition: Util.cpp:280
std::unique_ptr< WarningData > Data
Pointer to warning data. Must never be nullptr.
Definition: Util.h:1038
constexpr auto DynExpVersion
DynExp's version string
DynExp's Util namespace contains commonly used functions and templates as well as extensions to Qt an...
Definition: circularbuf.cpp:7
std::string ToStr(const T &Value, int Precision=-1)
Converts a (numeric) value of type T to a std::string using operator<< of std::stringstream.
Definition: Util.h:625
auto FilenameFromPath(std::string Path)
Extracts the filename from a path.
Definition: Util.h:846
std::string ExceptionToStr(const std::exception_ptr ExceptionPtr)
Returns the what() information of an exception derived from std::exception and stored in an exception...
Definition: Util.cpp:189
auto CurrentTimeAndDateString()
Returns a human-readable string describing the current time and date in the current time zone.
Definition: Util.h:839
VersionType VersionFromString(std::string_view Str)
Extracts a program version from a string.
Definition: Util.cpp:162
unsigned int Major
Definition: Util.h:860
EventLogger & EventLog()
This function holds a static EventLogger instance and returns a reference to it. DynExp uses only one...
Definition: Util.cpp:509
unsigned int Minor
Definition: Util.h:861
std::strong_ordering operator<=>(const VersionType &lhs, const VersionType &rhs)
Compares two program version types with each other.
Definition: Util.cpp:149
std::vector< std::complex< double > > FFT(const std::vector< std::complex< double >> &Data, bool InverseTransform)
Computes the Fast Fourier Transform (FFT) a vector of complex values.
Definition: Util.cpp:220
std::string ToLower(std::string_view Str)
Transforms a string into lower case.
Definition: Util.cpp:208
unsigned int Patch
Definition: Util.h:862
ErrorType
DynExp's error types
Definition: Exception.h:15
Data type describing DynExp's program version in the form Major.Minor.Patch.
Definition: Util.h:859
Accumulates include statements to provide a precompiled header.
#define DYNEXP_HAS_STACKTRACE
Definition: stdafx.h:60
Data type of a single entry in DynExp's log.
Definition: Util.h:1045
Data associated with a warning. The class is convertible to bool (true if it describes an error/warni...
Definition: Util.h:973