qxLib
assert_compare.inl
Go to the documentation of this file.
1 /**
2 
3  @file assert_compare.inl
4  @author Khrapov
5  @date 19.05.2026
6  @copyright (c) Nick Khrapov, 2026. All right reserved.
7 
8 **/
9 
10 namespace qx
11 {
12 
13 namespace details
14 {
15 
16 template<class operation_t>
18 
19 template<>
20 struct assert_operation_symbol<std::equal_to<>>
21 {
22  static constexpr string_view symbol() noexcept
23  {
24  return QXT("==");
25  }
26 };
27 
28 template<>
29 struct assert_operation_symbol<std::not_equal_to<>>
30 {
31  static constexpr string_view symbol() noexcept
32  {
33  return QXT("!=");
34  }
35 };
36 
37 template<>
38 struct assert_operation_symbol<std::less<>>
39 {
40  static constexpr string_view symbol() noexcept
41  {
42  return QXT("<");
43  }
44 };
45 
46 template<>
47 struct assert_operation_symbol<std::less_equal<>>
48 {
49  static constexpr string_view symbol() noexcept
50  {
51  return QXT("<=");
52  }
53 };
54 
55 template<>
56 struct assert_operation_symbol<std::greater<>>
57 {
58  static constexpr string_view symbol() noexcept
59  {
60  return QXT(">");
61  }
62 };
63 
64 template<>
65 struct assert_operation_symbol<std::greater_equal<>>
66 {
67  static constexpr string_view symbol() noexcept
68  {
69  return QXT(">=");
70  }
71 };
72 
73 constexpr string_view trim_assert_expression(string_view svExpression) noexcept
74 {
75  while (!svExpression.empty()
76  && (svExpression.front() == QXT(' ') || svExpression.front() == QXT('\t')
77  || svExpression.front() == QXT('\r') || svExpression.front() == QXT('\n')))
78  {
79  svExpression.remove_prefix(1);
80  }
81 
82  while (!svExpression.empty()
83  && (svExpression.back() == QXT(' ') || svExpression.back() == QXT('\t') || svExpression.back() == QXT('\r')
84  || svExpression.back() == QXT('\n')))
85  {
86  svExpression.remove_suffix(1);
87  }
88 
89  return svExpression;
90 }
91 
92 constexpr bool is_space(char_type ch) noexcept
93 {
94  return ch == QXT(' ') || ch == QXT('\t') || ch == QXT('\r') || ch == QXT('\n');
95 }
96 
97 constexpr bool is_char(char_type ch) noexcept
98 {
99  return ch == QXT('_') || ch >= QXT('0') && ch <= QXT('9') || ch >= QXT('a') && ch <= QXT('z')
100  || ch >= QXT('A') && ch <= QXT('Z');
101 }
102 
103 constexpr bool is_identifier_first_char(char_type ch) noexcept
104 {
105  return ch == QXT('_') || ch >= QXT('a') && ch <= QXT('z') || ch >= QXT('A') && ch <= QXT('Z');
106 }
107 
108 constexpr bool is_literal(string_view svExpression) noexcept
109 {
110  if (svExpression.empty())
111  return false;
112 
113  if (svExpression == QXT("true") || svExpression == QXT("false") || svExpression == QXT("nullptr"))
114  return true;
115 
116  const char_type chFirst = svExpression.front();
117  if (chFirst == QXT('"') || chFirst == QXT('\''))
118  return true;
119 
120  if (chFirst >= QXT('0') && chFirst <= QXT('9'))
121  return true;
122 
123  return (chFirst == QXT('+') || chFirst == QXT('-')) && svExpression.size() > 1 && svExpression[1] >= QXT('0')
124  && svExpression[1] <= QXT('9');
125 }
126 
127 constexpr bool is_temporary_value(string_view svExpression) noexcept
128 {
129  if (svExpression.starts_with(QXT("static_cast<")) || svExpression.starts_with(QXT("const_cast<"))
130  || svExpression.starts_with(QXT("dynamic_cast<")) || svExpression.starts_with(QXT("reinterpret_cast<")))
131  {
132  return false;
133  }
134 
135  bool bQualified = false;
136  size_t nPos = 0;
137  while (nPos < svExpression.size() && is_char(svExpression[nPos]))
138  ++nPos;
139 
140  while (nPos + 1 < svExpression.size() && svExpression[nPos] == QXT(':') && svExpression[nPos + 1] == QXT(':'))
141  {
142  bQualified = true;
143  nPos += 2;
144  while (nPos < svExpression.size() && is_char(svExpression[nPos]))
145  ++nPos;
146  }
147 
148  if (nPos < svExpression.size() && svExpression[nPos] == QXT('<'))
149  {
150  size_t nAngleDepth = 1;
151  ++nPos;
152 
153  while (nPos < svExpression.size() && nAngleDepth > 0)
154  {
155  if (svExpression[nPos] == QXT('<'))
156  ++nAngleDepth;
157  else if (svExpression[nPos] == QXT('>'))
158  --nAngleDepth;
159 
160  ++nPos;
161  }
162  }
163 
164  return bQualified && nPos < svExpression.size() && svExpression[nPos] == QXT('(');
165 }
166 
167 constexpr bool is_qualified_value(string_view svExpression) noexcept
168 {
169  size_t nPos = 0;
170  if (svExpression.size() > 1 && svExpression[0] == QXT(':') && svExpression[1] == QXT(':'))
171  nPos = 2;
172 
173  bool bQualified = nPos != 0;
174  while (nPos < svExpression.size())
175  {
176  if (!is_identifier_first_char(svExpression[nPos]))
177  return false;
178 
179  while (nPos < svExpression.size() && is_char(svExpression[nPos]))
180  ++nPos;
181 
182  if (nPos < svExpression.size() && svExpression[nPos] == QXT('<'))
183  {
184  size_t nAngleDepth = 1;
185  ++nPos;
186 
187  while (nPos < svExpression.size() && nAngleDepth > 0)
188  {
189  if (svExpression[nPos] == QXT('<'))
190  ++nAngleDepth;
191  else if (svExpression[nPos] == QXT('>'))
192  --nAngleDepth;
193 
194  ++nPos;
195  }
196 
197  if (nAngleDepth != 0)
198  return false;
199  }
200 
201  if (nPos == svExpression.size())
202  return bQualified;
203 
204  if (nPos + 1 >= svExpression.size() || svExpression[nPos] != QXT(':') || svExpression[nPos + 1] != QXT(':'))
205  {
206  return false;
207  }
208 
209  bQualified = true;
210  nPos += 2;
211  }
212 
213  return false;
214 }
215 
216 constexpr bool should_show_assert_expression_for_rvalue(string_view svExpression) noexcept
217 {
218  return !is_literal(svExpression) && !is_temporary_value(svExpression) && !is_qualified_value(svExpression);
219 }
220 
221 constexpr bool is_identifier_open_angle_char(char_type ch) noexcept
222 {
223  return ch == QXT('_') || ch >= QXT('a') && ch <= QXT('z') || ch >= QXT('A') && ch <= QXT('Z');
224 }
225 
226 constexpr bool is_open_angle(string_view svExpression, size_t nPos) noexcept
227 {
228  if (nPos == 0)
229  return false;
230 
231  const char_type chPrev = svExpression[nPos - 1];
232  return is_identifier_open_angle_char(chPrev) || chPrev == QXT(':') || chPrev == QXT('>') || chPrev == QXT('&')
233  || chPrev == QXT('*');
234 }
235 
236 constexpr bool is_close_angle(string_view svExpression, size_t nPos) noexcept
237 {
238  if (nPos + 1 >= svExpression.size())
239  return true;
240 
241  const char_type chNext = svExpression[nPos + 1];
242  return is_space(chNext) || chNext == QXT('(') || chNext == QXT(')') || chNext == QXT(',') || chNext == QXT(':')
243  || chNext == QXT('&') || chNext == QXT('*') || chNext == QXT('>') || chNext == QXT('{')
244  || chNext == QXT('.');
245 }
246 
247 constexpr std::pair<string_view, string_view> split_assert_arguments(string_view svCondition) noexcept
248 {
249  const size_t nArgsBegin = svCondition.find(QXT('('));
250  if (nArgsBegin == string_view::npos)
251  return {};
252 
253  size_t nParenDepth = 0;
254  size_t nBracketDepth = 0;
255  size_t nBraceDepth = 0;
256  size_t nAngleDepth = 0;
257  size_t nCommaPos = string_view::npos;
258  size_t nArgsEnd = string_view::npos;
259  bool bInString = false;
260  bool bInChar = false;
261  bool bEscaped = false;
262 
263  for (size_t nPos = nArgsBegin + 1; nPos < svCondition.size(); ++nPos)
264  {
265  if (bInString || bInChar)
266  {
267  if (bEscaped)
268  {
269  bEscaped = false;
270  }
271  else if (svCondition[nPos] == QXT('\\'))
272  {
273  bEscaped = true;
274  }
275  else if (bInString && svCondition[nPos] == QXT('"'))
276  {
277  bInString = false;
278  }
279  else if (bInChar && svCondition[nPos] == QXT('\''))
280  {
281  bInChar = false;
282  }
283 
284  continue;
285  }
286 
287  switch (svCondition[nPos])
288  {
289  case QXT('"'):
290  bInString = true;
291  break;
292 
293  case QXT('\''):
294  bInChar = true;
295  break;
296 
297  case QXT('('):
298  ++nParenDepth;
299  break;
300 
301  case QXT(')'):
302  if (nParenDepth == 0 && nBracketDepth == 0 && nBraceDepth == 0 && nAngleDepth == 0)
303  {
304  nArgsEnd = nPos;
305  nPos = svCondition.size();
306  }
307  else
308  {
309  if (nParenDepth == 0)
310  return {};
311  --nParenDepth;
312  }
313  break;
314 
315  case QXT('['):
316  ++nBracketDepth;
317  break;
318 
319  case QXT(']'):
320  if (nBracketDepth == 0)
321  return {};
322  --nBracketDepth;
323  break;
324 
325  case QXT('{'):
326  ++nBraceDepth;
327  break;
328 
329  case QXT('}'):
330  if (nBraceDepth == 0)
331  return {};
332  --nBraceDepth;
333  break;
334 
335  case QXT('<'):
336  if (is_open_angle(svCondition, nPos))
337  ++nAngleDepth;
338  break;
339 
340  case QXT('>'):
341  if (nAngleDepth > 0 && is_close_angle(svCondition, nPos))
342  --nAngleDepth;
343  break;
344 
345  case QXT(','):
346  if (nParenDepth == 0 && nBracketDepth == 0 && nBraceDepth == 0 && nAngleDepth == 0
347  && nCommaPos == string_view::npos)
348  nCommaPos = nPos;
349  break;
350  }
351  }
352 
353  if (nCommaPos == string_view::npos || nArgsEnd == string_view::npos)
354  return {};
355 
356  return { trim_assert_expression(svCondition.substr(nArgsBegin + 1, nCommaPos - nArgsBegin - 1)),
357  trim_assert_expression(svCondition.substr(nCommaPos + 1, nArgsEnd - nCommaPos - 1)) };
358 }
359 
360 template<class T>
361 inline string assert_value_to_string(const T& value) noexcept
362 {
363  if constexpr (std::is_convertible_v<const T&, string_view>)
364  return string(value);
365  else
366  return qx::convert_to_string(value);
367 }
368 
369 inline void append_assert_expression(string& sResult, string_view svExpression) noexcept
370 {
371  bool bPreviousWasSpace = false;
372 
373  for (const char_type ch : svExpression)
374  {
375  if (is_space(ch))
376  {
377  bPreviousWasSpace = !sResult.empty();
378  }
379  else
380  {
381  if (bPreviousWasSpace)
382  {
383  sResult.append(QXT(' '));
384  bPreviousWasSpace = false;
385  }
386 
387  sResult.append(ch);
388  }
389  }
390 }
391 
392 template<bool bCanHaveName, class T>
393 inline string make_assert_operand_string(string_view svExpression, const T& value) noexcept
394 {
395  if constexpr (bCanHaveName)
396  {
397  string sResult;
398  append_assert_expression(sResult, svExpression);
399  sResult.append(QXT(" ["));
400  sResult.append(assert_value_to_string(value));
401  sResult.append(QXT("]"));
402  return sResult;
403  }
404  else
405  {
406  return assert_value_to_string(value);
407  }
408 }
409 
410 template<string_literal svCondition, class condition_t>
411 constexpr string_view get_assert_condition_string(const condition_t&) noexcept
412 {
413  return svCondition.view();
414 }
415 
416 template<
417  string_literal svCondition,
418  class left_t,
419  class right_t,
420  class operation_t,
421  bool bLeftIsLvalue,
422  bool bRightIsLvalue>
423 inline string get_assert_condition_string(
424  const assert_comparison<left_t, right_t, operation_t, bLeftIsLvalue, bRightIsLvalue>& condition) noexcept
425 {
426  constexpr auto split = split_assert_arguments(svCondition.view());
427  constexpr auto svLeftExpression = split.first;
428  constexpr auto svRightExpression = split.second;
429 
430  return qx::format(
431  QXT("{} {} {}"),
432  make_assert_operand_string < bLeftIsLvalue
433  || should_show_assert_expression_for_rvalue(svLeftExpression) > (svLeftExpression, condition.left()),
434  assert_operation_symbol<operation_t>::symbol(),
435  make_assert_operand_string < bRightIsLvalue
436  || should_show_assert_expression_for_rvalue(svRightExpression) > (svRightExpression, condition.right()));
437 }
438 
439 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
441  assert_comparison(left_t&& left, right_t&& right) noexcept(
442  std::is_nothrow_constructible_v<left_t, left_t&&> && std::is_nothrow_constructible_v<right_t, right_t&&>)
443  : m_Left(std::forward<left_t>(left))
444  , m_Right(std::forward<right_t>(right))
445 {
446 }
447 
448 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
450  noexcept(noexcept(operation_t {}(left(), right())))
451 {
452  return operation_t {}(left(), right());
453 }
454 
455 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
457  noexcept(noexcept(result()))
458 {
459  return result();
460 }
461 
462 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
464  const noexcept
465 {
466  return m_Left;
467 }
468 
469 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
471  const noexcept
472 {
473  return m_Right;
474 }
475 
476 } // namespace details
477 
478 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
479 constexpr bool predicates::
480  validator<details::assert_comparison<left_t, right_t, operation_t, bLeftIsLvalue, bRightIsLvalue>>::is_valid(
482  value) noexcept(noexcept(value.result()))
483 {
484  return value.result();
485 }
486 
487 template<class left_t, class right_t>
488 constexpr auto assert_eq(left_t&& left, right_t&& right) noexcept(
490  left_t,
491  right_t,
492  std::equal_to<>,
493  std::is_lvalue_reference_v<left_t>,
494  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
495 {
497  left_t,
498  right_t,
499  std::equal_to<>,
500  std::is_lvalue_reference_v<left_t>,
501  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
502 }
503 
504 template<class left_t, class right_t>
505 constexpr auto assert_ne(left_t&& left, right_t&& right) noexcept(
507  left_t,
508  right_t,
509  std::not_equal_to<>,
510  std::is_lvalue_reference_v<left_t>,
511  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
512 {
514  left_t,
515  right_t,
516  std::not_equal_to<>,
517  std::is_lvalue_reference_v<left_t>,
518  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
519 }
520 
521 template<class left_t, class right_t>
522 constexpr auto assert_lt(left_t&& left, right_t&& right) noexcept(
524  left_t,
525  right_t,
526  std::less<>,
527  std::is_lvalue_reference_v<left_t>,
528  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
529 {
531  left_t,
532  right_t,
533  std::less<>,
534  std::is_lvalue_reference_v<left_t>,
535  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
536 }
537 
538 template<class left_t, class right_t>
539 constexpr auto assert_le(left_t&& left, right_t&& right) noexcept(
541  left_t,
542  right_t,
543  std::less_equal<>,
544  std::is_lvalue_reference_v<left_t>,
545  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
546 {
548  left_t,
549  right_t,
550  std::less_equal<>,
551  std::is_lvalue_reference_v<left_t>,
552  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
553 }
554 
555 template<class left_t, class right_t>
556 constexpr auto assert_gt(left_t&& left, right_t&& right) noexcept(
558  left_t,
559  right_t,
560  std::greater<>,
561  std::is_lvalue_reference_v<left_t>,
562  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
563 {
565  left_t,
566  right_t,
567  std::greater<>,
568  std::is_lvalue_reference_v<left_t>,
569  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
570 }
571 
572 template<class left_t, class right_t>
573 constexpr auto assert_ge(left_t&& left, right_t&& right) noexcept(
575  left_t,
576  right_t,
577  std::greater_equal<>,
578  std::is_lvalue_reference_v<left_t>,
579  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
580 {
582  left_t,
583  right_t,
584  std::greater_equal<>,
585  std::is_lvalue_reference_v<left_t>,
586  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
587 }
588 
589 } // namespace qx
constexpr auto assert_ge(left_t &&left, right_t &&right) noexcept(noexcept(details::assert_comparison< left_t, right_t, std::greater_equal<>, std::is_lvalue_reference_v< left_t >, std::is_lvalue_reference_v< right_t >>(std::forward< left_t >(left), std::forward< right_t >(right))))
Compare whether left value is greater than or equal to right value and preserve values for assertion ...
constexpr auto assert_le(left_t &&left, right_t &&right) noexcept(noexcept(details::assert_comparison< left_t, right_t, std::less_equal<>, std::is_lvalue_reference_v< left_t >, std::is_lvalue_reference_v< right_t >>(std::forward< left_t >(left), std::forward< right_t >(right))))
Compare whether left value is less than or equal to right value and preserve values for assertion dia...
constexpr auto assert_gt(left_t &&left, right_t &&right) noexcept(noexcept(details::assert_comparison< left_t, right_t, std::greater<>, std::is_lvalue_reference_v< left_t >, std::is_lvalue_reference_v< right_t >>(std::forward< left_t >(left), std::forward< right_t >(right))))
Compare whether left value is greater than right value and preserve values for assertion diagnostics.
constexpr auto assert_lt(left_t &&left, right_t &&right) noexcept(noexcept(details::assert_comparison< left_t, right_t, std::less<>, std::is_lvalue_reference_v< left_t >, std::is_lvalue_reference_v< right_t >>(std::forward< left_t >(left), std::forward< right_t >(right))))
Compare whether left value is less than right value and preserve values for assertion diagnostics.
constexpr auto assert_ne(left_t &&left, right_t &&right) noexcept(noexcept(details::assert_comparison< left_t, right_t, std::not_equal_to<>, std::is_lvalue_reference_v< left_t >, std::is_lvalue_reference_v< right_t >>(std::forward< left_t >(left), std::forward< right_t >(right))))
Compare two values for inequality and preserve values for assertion diagnostics.
constexpr auto assert_eq(left_t &&left, right_t &&right) noexcept(noexcept(details::assert_comparison< left_t, right_t, std::equal_to<>, std::is_lvalue_reference_v< left_t >, std::is_lvalue_reference_v< right_t >>(std::forward< left_t >(left), std::forward< right_t >(right))))
Compare two values for equality and preserve values for assertion diagnostics.
Lightweight assertion comparison object that preserves compared operand values for diagnostics.
constexpr const auto & left() const noexcept
Get left operand.
constexpr const auto & right() const noexcept
Get right operand.
constexpr bool result() const noexcept(noexcept(operation_t {}(left(), right())))
Evaluate comparison.
constexpr assert_comparison(left_t &&left, right_t &&right) noexcept(std::is_nothrow_constructible_v< left_t, left_t && > &&std::is_nothrow_constructible_v< right_t, right_t && >)
Construct comparison object from two operands.
requires format_acceptable_args_c< char, args_t... > cstring format(const QX_FMT_NS::format_string< std::type_identity_t< args_t >... > sFormat, args_t &&... args) noexcept
std::format / fmt::format wrapper that returns qx::string
Definition: format.h:26
basic_string< char_t, traits_t > convert_to_string(const T &value) noexcept
Converts any type that has a QX_FMT_NS::formatter overload to qx::basic_string.
Definition: format.h:67