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  i32 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  i32 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  i32 nParenDepth = 0;
254  i32 nBracketDepth = 0;
255  i32 nBraceDepth = 0;
256  i32 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  --nParenDepth;
310  }
311  break;
312 
313  case QXT('['):
314  ++nBracketDepth;
315  break;
316 
317  case QXT(']'):
318  --nBracketDepth;
319  break;
320 
321  case QXT('{'):
322  ++nBraceDepth;
323  break;
324 
325  case QXT('}'):
326  --nBraceDepth;
327  break;
328 
329  case QXT('<'):
330  if (is_open_angle(svCondition, nPos))
331  ++nAngleDepth;
332  break;
333 
334  case QXT('>'):
335  if (nAngleDepth > 0 && is_close_angle(svCondition, nPos))
336  --nAngleDepth;
337  break;
338 
339  case QXT(','):
340  if (nParenDepth == 0 && nBracketDepth == 0 && nBraceDepth == 0 && nAngleDepth == 0
341  && nCommaPos == string_view::npos)
342  nCommaPos = nPos;
343  break;
344  }
345  }
346 
347  if (nCommaPos == string_view::npos || nArgsEnd == string_view::npos)
348  return {};
349 
350  return { trim_assert_expression(svCondition.substr(nArgsBegin + 1, nCommaPos - nArgsBegin - 1)),
351  trim_assert_expression(svCondition.substr(nCommaPos + 1, nArgsEnd - nCommaPos - 1)) };
352 }
353 
354 template<class T>
355 inline string assert_value_to_string(const T& value) noexcept
356 {
357  if constexpr (std::is_convertible_v<const T&, string_view>)
358  return string(value);
359  else
360  return qx::convert_to_string(value);
361 }
362 
363 inline void append_assert_expression(string& sResult, string_view svExpression) noexcept
364 {
365  bool bPreviousWasSpace = false;
366 
367  for (const char_type ch : svExpression)
368  {
369  if (is_space(ch))
370  {
371  bPreviousWasSpace = !sResult.empty();
372  }
373  else
374  {
375  if (bPreviousWasSpace)
376  {
377  sResult.append(QXT(' '));
378  bPreviousWasSpace = false;
379  }
380 
381  sResult.append(ch);
382  }
383  }
384 }
385 
386 template<bool bCanHaveName, class T>
387 inline string make_assert_operand_string(string_view svExpression, const T& value) noexcept
388 {
389  if constexpr (bCanHaveName)
390  {
391  string sResult;
392  append_assert_expression(sResult, svExpression);
393  sResult.append(QXT(" ["));
394  sResult.append(assert_value_to_string(value));
395  sResult.append(QXT("]"));
396  return sResult;
397  }
398  else
399  {
400  return assert_value_to_string(value);
401  }
402 }
403 
404 template<string_literal svCondition, class condition_t>
405 constexpr string_view get_assert_condition_string(const condition_t&) noexcept
406 {
407  return svCondition.view();
408 }
409 
410 template<
411  string_literal svCondition,
412  class left_t,
413  class right_t,
414  class operation_t,
415  bool bLeftIsLvalue,
416  bool bRightIsLvalue>
417 inline string get_assert_condition_string(
418  const assert_comparison<left_t, right_t, operation_t, bLeftIsLvalue, bRightIsLvalue>& condition) noexcept
419 {
420  constexpr auto split = split_assert_arguments(svCondition.view());
421  constexpr auto svLeftExpression = split.first;
422  constexpr auto svRightExpression = split.second;
423 
424  return qx::format(
425  QXT("{} {} {}"),
426  make_assert_operand_string < bLeftIsLvalue
427  || should_show_assert_expression_for_rvalue(svLeftExpression) > (svLeftExpression, condition.left()),
428  assert_operation_symbol<operation_t>::symbol(),
429  make_assert_operand_string < bRightIsLvalue
430  || should_show_assert_expression_for_rvalue(svRightExpression) > (svRightExpression, condition.right()));
431 }
432 
433 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
435  assert_comparison(left_t&& left, right_t&& right) noexcept(
436  std::is_nothrow_constructible_v<left_t, left_t&&> && std::is_nothrow_constructible_v<right_t, right_t&&>)
437  : m_Left(std::forward<left_t>(left))
438  , m_Right(std::forward<right_t>(right))
439 {
440 }
441 
442 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
444  noexcept(noexcept(operation_t {}(left(), right())))
445 {
446  return operation_t {}(left(), right());
447 }
448 
449 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
451  noexcept(noexcept(result()))
452 {
453  return result();
454 }
455 
456 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
458  const noexcept
459 {
460  return m_Left;
461 }
462 
463 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
465  const noexcept
466 {
467  return m_Right;
468 }
469 
470 } // namespace details
471 
472 template<class left_t, class right_t, class operation_t, bool bLeftIsLvalue, bool bRightIsLvalue>
473 constexpr bool predicates::
474  validator<details::assert_comparison<left_t, right_t, operation_t, bLeftIsLvalue, bRightIsLvalue>>::is_valid(
476  value) noexcept(noexcept(value.result()))
477 {
478  return value.result();
479 }
480 
481 template<class left_t, class right_t>
482 constexpr auto assert_eq(left_t&& left, right_t&& right) noexcept(
484  left_t,
485  right_t,
486  std::equal_to<>,
487  std::is_lvalue_reference_v<left_t>,
488  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
489 {
491  left_t,
492  right_t,
493  std::equal_to<>,
494  std::is_lvalue_reference_v<left_t>,
495  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
496 }
497 
498 template<class left_t, class right_t>
499 constexpr auto assert_ne(left_t&& left, right_t&& right) noexcept(
501  left_t,
502  right_t,
503  std::not_equal_to<>,
504  std::is_lvalue_reference_v<left_t>,
505  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
506 {
508  left_t,
509  right_t,
510  std::not_equal_to<>,
511  std::is_lvalue_reference_v<left_t>,
512  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
513 }
514 
515 template<class left_t, class right_t>
516 constexpr auto assert_lt(left_t&& left, right_t&& right) noexcept(
518  left_t,
519  right_t,
520  std::less<>,
521  std::is_lvalue_reference_v<left_t>,
522  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
523 {
525  left_t,
526  right_t,
527  std::less<>,
528  std::is_lvalue_reference_v<left_t>,
529  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
530 }
531 
532 template<class left_t, class right_t>
533 constexpr auto assert_le(left_t&& left, right_t&& right) noexcept(
535  left_t,
536  right_t,
537  std::less_equal<>,
538  std::is_lvalue_reference_v<left_t>,
539  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
540 {
542  left_t,
543  right_t,
544  std::less_equal<>,
545  std::is_lvalue_reference_v<left_t>,
546  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
547 }
548 
549 template<class left_t, class right_t>
550 constexpr auto assert_gt(left_t&& left, right_t&& right) noexcept(
552  left_t,
553  right_t,
554  std::greater<>,
555  std::is_lvalue_reference_v<left_t>,
556  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
557 {
559  left_t,
560  right_t,
561  std::greater<>,
562  std::is_lvalue_reference_v<left_t>,
563  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
564 }
565 
566 template<class left_t, class right_t>
567 constexpr auto assert_ge(left_t&& left, right_t&& right) noexcept(
569  left_t,
570  right_t,
571  std::greater_equal<>,
572  std::is_lvalue_reference_v<left_t>,
573  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right))))
574 {
576  left_t,
577  right_t,
578  std::greater_equal<>,
579  std::is_lvalue_reference_v<left_t>,
580  std::is_lvalue_reference_v<right_t>>(std::forward<left_t>(left), std::forward<right_t>(right));
581 }
582 
583 } // 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:25
basic_string< char_t, traits_t > convert_to_string(const T &value) noexcept
Converts any type that has a std::formatter overload to qx::basic_string.
Definition: format.h:61
std::int32_t i32
− 9 223 372 036 854 775 808 .. 9 223 372 036 854 775 807
Definition: typedefs.h:32