Skip to main content

detail-inlines.h File

C++ header file with inline implementations for the µTest++ internal detail namespace. More...

Included Headers​

#include <algorithm> #include <cerrno> #include <cfloat> #include <charconv> #include <cstdint> #include <cstdio> #include <cstdlib> #include <exception> #include <limits>

Namespaces Index​

namespacemicro_os_plus

The primary namespace for the µOS++ framework. More...

namespacemicro_test_plus

Primary namespace for the µTest++ testing framework. More...

namespacedetail

Internal implementation details for the µTest++ framework. More...

namespacecomparison_lookup

Names for the checks of the comparison operators, looked up as the apply() functions look them up. More...

Classes Index​

structcomparison_integer<T>

The integer type used to compare a value of type T. More...

structcomparison_integer<T>

The integer type used to compare a character type. More...

Concepts Index​

conceptcomparable

Concept satisfied when two operands can be compared by the comparator selected by Op. More...

conceptcomparable_operands

Concept satisfied when the operands of a comparator can be written and compared. More...

conceptcan_compare

Satisfied if the comparison selected by Op of two const objects is well-formed. More...

conceptfunction_pointer_pair

Concept satisfied by two pointers, one of which points to a function. More...

conceptsign_mixed_integers

Concept satisfied by two integer types of different signedness. More...

concepttag_has_symbol

Concept satisfied when the tag of a comparator or of a predicate has a symbol that can be written. More...

Typedefs Index​

template <class T>
usingcomparison_integer_t = typename comparison_integer< T >::type

Alias of the integer type used to compare the type T. More...

template <class T>
usingcomparison_operand_t = unwrapped_t< decltype(detail::as_operand(std::declval< T >()))>

The type with which an operand of a comparator is compared. More...

Enumerations Index​

enum classcomparison : unsigned char { ... }

The comparison operators, as the template argument of the comparable concept. More...

Operators Index​

template <class T, class R>
constexpr autooperator<=> (const array_operand< T > &lhs, const R &rhs)

Orders an array operand and a range lexicographically. More...

template <character Char_T>
constexpr std::strong_orderingoperator<=> (const basic_c_string< Char_T > &lhs, const basic_c_string< Char_T > &rhs) noexcept

Orders two C string operands. More...

template <character Char_T>
constexpr std::strong_orderingoperator<=> (const basic_c_string< Char_T > &lhs, std::type_identity_t< std::basic_string_view< Char_T > > rhs) noexcept

Orders a C string operand and a string. More...

template <class T, class R>
constexpr booloperator== (const array_operand< T > &lhs, const R &rhs)

Compares an array operand with a range for equality. More...

template <character Char_T>
constexpr booloperator== (const basic_c_string< Char_T > &lhs, const basic_c_string< Char_T > &rhs) noexcept

Compares two C string operands for equality. More...

template <character Char_T>
constexpr booloperator== (const basic_c_string< Char_T > &lhs, std::nullptr_t) noexcept

Checks whether a C string operand is a null pointer. More...

template <character Char_T>
constexpr booloperator== (const basic_c_string< Char_T > &lhs, std::type_identity_t< std::basic_string_view< Char_T > > rhs) noexcept

Compares a C string operand with a string. More...

Functions Index​

template <class T>
voidappend_number (std::string &buffer, T v)

Appends the shortest decimal representation of a numeric value to a buffer. More...

template <class T>
voidappend_pointer (std::string &buffer, T *v)

Appends the hexadecimal address of a pointer to a buffer. More...

template <class T>
constexpr auto as_comparison_integer (const T value) noexcept -> comparison_integer_t< T >

Converts an integer or a character to the type used to compare it. More...

template <class T>
constexpr auto as_operand (T &&t) noexcept -> decltype(auto)

Prepares an operand of a comparator. More...

template <class Lhs_T, class Rhs_T, class Epsilon_T>
constexpr booldiffer_by_at_most (Lhs_T lhs, Rhs_T rhs, Epsilon_T epsilon)

Tells whether two numbers differ by at most a tolerance. More...

template <class Tag_T>
constexpr voidreject_compare_tag (void) noexcept

Stops the compilation of a compare_op whose tag is rejected, with a message that tells why. More...

template <comparison Op, class Lhs_T, class Rhs_T>
constexpr voidreject_comparison (void) noexcept

Stops the compilation of a comparator whose operands are rejected, with a message that tells why. More...

template <class Tag_T>
constexpr voidreject_predicate_tag (void) noexcept

Stops the compilation of a predicate_op whose tag is rejected, with a message that tells why. More...

template <class T>
constexpr auto unwrap (const T &t) -> decltype(auto)

Returns the value wrapped by a framework type, or the operand itself. More...

template <class Lhs_T, class Rhs_T>
constexpr boolwithin_tolerance (const Lhs_T &lhs, const Rhs_T &rhs)

Deprecated check whether two operands are equal within the tolerance of the floating-point wrappers. More...

Description​

C++ header file with inline implementations for the µTest++ internal detail namespace.

This header provides the out-of-line template and inline implementations for all types declared in detail.h. It defines the bodies of the unwrap() helper, the unary and binary operator base class templates, the comparison tags, all relational, approximate, and logical comparator constructors, the predicate constructor, the tolerance helpers (within_tolerance(), differ_by_at_most()), the callable operator types (throws_op, nothrow_op), the append_number() and append_pointer() formatting helpers, the basic_c_string operand and its operators, and as_operand().

Separating the implementations from the declarations keeps detail.h concise and focused on the interface, whilst grouping the complex lambda-based constructor bodies here for maintainability.

All definitions reside within the micro_os_plus::micro_test_plus::detail namespace.

This file is included by detail.h; the applications include micro-os-plus/micro-test-plus.h, not this file.

Typedefs​

comparison_integer_t​

template <class T>
using micro_os_plus::micro_test_plus::detail::comparison_integer_t = typename comparison_integer<T>::type

Alias of the integer type used to compare the type T.

Used by sign_mixed_integers, to tell the signedness, and by as_comparison_integer(), to convert the operands.

Template Parameters
T

The type of the operand.

Definition at line 326 of file detail-inlines.h.

comparison_operand_t​

template <class T>
using micro_os_plus::micro_test_plus::detail::comparison_operand_t = unwrapped_t<decltype (detail::as_operand (std::declval<T> ()))>

The type with which an operand of a comparator is compared.

The type of the operand after as_operand() and unwrap(), without references and qualifiers; for example detail::basic_c_string<char> for a string literal.

Template Parameters
T

The type deduced for the operand, possibly a reference.

Definition at line 386 of file detail-inlines.h.

Enumerations​

comparison​

enum class micro_os_plus::micro_test_plus::detail::comparison : unsigned char
strong

The comparison operators, as the template argument of the comparable concept.

Enumeration values
equalThe == operator, checked by eq()
not_equalThe != operator, checked by ne()
greaterThe > operator, checked by gt()
greater_equalThe >= operator, checked by ge()
lessThe < operator, checked by lt()
less_equalThe <= operator, checked by le()

A single concept, parametrised by the operator, checks the operands of all the comparators (eq(), ne(), gt(), ge(), lt(), and le()).

Definition at line 416 of file detail-inlines.h.

417 enum class comparison : unsigned char
418 {
420 equal,
424 greater,
428 less,

Operators​

operator<=>()​

template <class T, class R>
auto micro_os_plus::micro_test_plus::detail::operator<=> (const array_operand< T > & lhs, const R & rhs)
nodiscard constexpr

Orders an array operand and a range lexicographically.

Template Parameters
T

The element type of the array operand.

R

The type of the range.

Parameters
lhs

The array operand.

rhs

The range, which may be another array operand.

Returns

The lexicographic order of the elements.

A range that is a prefix of the other is the lesser one.

Definition at line 1696 of file detail-inlines.h.

1697 constexpr auto
1698 operator<=> (const array_operand<T>& lhs, const R& rhs)
1699 {
1700 return std::lexicographical_compare_three_way (lhs.begin (), lhs.end (),
1701 rhs.begin (), rhs.end ());

References micro_os_plus::micro_test_plus::detail::array_operand< T >::begin, micro_os_plus::micro_test_plus::detail::array_operand< T >::end and micro_os_plus::micro_test_plus::detail::operator<=>.

operator<=>()​

template <character Char_T>
std::strong_ordering micro_os_plus::micro_test_plus::detail::operator<=> (const basic_c_string< Char_T > & lhs, const basic_c_string< Char_T > & rhs)
nodiscard constexpr noexcept

Orders two C string operands.

Template Parameters
Char_T

The character type.

Parameters
lhs

The left operand.

rhs

The right operand.

Returns

The lexicographic order of the characters; a null pointer is equal to another null pointer and less than any string.

A null pointer orders before any string.

Definition at line 1594 of file detail-inlines.h.

1595 constexpr std::strong_ordering
1597 const basic_c_string<Char_T>& rhs) noexcept
1598 {
1599 if (lhs.is_null () || rhs.is_null ())
1600 {
1601 return !lhs.is_null () <=> !rhs.is_null ();
1602 }
1603 return lhs.view () <=> rhs.view ();

Referenced by micro_os_plus::micro_test_plus::detail::operator<=>.

operator<=>()​

template <character Char_T>
std::strong_ordering micro_os_plus::micro_test_plus::detail::operator<=> (const basic_c_string< Char_T > & lhs, std::type_identity_t< std::basic_string_view< Char_T > > rhs)
nodiscard constexpr noexcept

Orders a C string operand and a string.

Template Parameters
Char_T

The character type.

Parameters
lhs

The C string operand.

rhs

The string.

Returns

The lexicographic order of the characters; a null pointer is less than any string.

A null pointer orders before any string.

Definition at line 1610 of file detail-inlines.h.

1611 constexpr std::strong_ordering
1613 const basic_c_string<Char_T>& lhs,
1614 std::type_identity_t<std::basic_string_view<Char_T>> rhs) noexcept
1615 {
1616 if (lhs.is_null ())
1617 {
1618 return std::strong_ordering::less;
1619 }
1620 return lhs.view () <=> rhs;

operator==()​

template <class T, class R>
bool micro_os_plus::micro_test_plus::detail::operator== (const array_operand< T > & lhs, const R & rhs)
nodiscard constexpr

Compares an array operand with a range for equality.

Template Parameters
T

The element type of the array operand.

R

The type of the range.

Parameters
lhs

The array operand.

rhs

The range, which may be another array operand.

Return Values
true

The ranges have the same size and equal elements.

false

Otherwise.

Ranges of different sizes are not equal, without comparing any element. The elements are compared as the scalar operands of eq() are, with equal_to::apply(), so that integers of different signedness are compared by value, not by converting the signed one to the unsigned type.

Definition at line 1681 of file detail-inlines.h.

1681 constexpr bool
1682 operator== (const array_operand<T>& lhs, const R& rhs)
1683 {
1684 return std::equal (lhs.begin (), lhs.end (), rhs.begin (), rhs.end (),
1685 [] (const auto& l, const auto& r)
1686 { return equal_to::apply (l, r); });

References micro_os_plus::micro_test_plus::detail::array_operand< T >::begin and micro_os_plus::micro_test_plus::detail::array_operand< T >::end.

operator==()​

template <character Char_T>
bool micro_os_plus::micro_test_plus::detail::operator== (const basic_c_string< Char_T > & lhs, const basic_c_string< Char_T > & rhs)
nodiscard constexpr noexcept

Compares two C string operands for equality.

Template Parameters
Char_T

The character type.

Parameters
lhs

The left operand.

rhs

The right operand.

Return Values
true

Both are null, or both are strings with the same characters.

false

Otherwise.

Two null pointers are equal; a null pointer is not equal to any string, not even an empty one.

Definition at line 1555 of file detail-inlines.h.

1555 constexpr bool
1557 const basic_c_string<Char_T>& rhs) noexcept
1558 {
1559 if (lhs.is_null () || rhs.is_null ())
1560 {
1561 return lhs.is_null () && rhs.is_null ();
1562 }
1563 return lhs.view () == rhs.view ();

operator==()​

template <character Char_T>
bool micro_os_plus::micro_test_plus::detail::operator== (const basic_c_string< Char_T > & lhs, std::nullptr_t)
nodiscard constexpr noexcept

Checks whether a C string operand is a null pointer.

Template Parameters
Char_T

The character type.

Parameters
lhs

The C string operand.

Return Values
true

The operand is a null pointer.

false

Otherwise.

Returns whether the operand is a null pointer.

Definition at line 1584 of file detail-inlines.h.

1584 constexpr bool
1585 operator==(const basic_c_string<Char_T>& lhs, std::nullptr_t) noexcept
1586 {
1587 return lhs.is_null ();

operator==()​

template <character Char_T>
bool micro_os_plus::micro_test_plus::detail::operator== (const basic_c_string< Char_T > & lhs, std::type_identity_t< std::basic_string_view< Char_T > > rhs)
nodiscard constexpr noexcept

Compares a C string operand with a string.

Also used for the standard strings of the same character type, which convert to the string view, and, as a rewritten candidate, with the operands in the reverse order.

Template Parameters
Char_T

The character type.

Parameters
lhs

The C string operand.

rhs

The string.

Return Values
true

The operand is not null and has the same characters.

false

Otherwise.

A null pointer is not equal to any string.

Definition at line 1571 of file detail-inlines.h.

1571 constexpr bool
1573 const basic_c_string<Char_T>& lhs,
1574 std::type_identity_t<std::basic_string_view<Char_T>> rhs) noexcept
1575 {
1576 return !lhs.is_null () && lhs.view () == rhs;

Functions​

append_number()​

template <class T>
void micro_os_plus::micro_test_plus::detail::append_number (std::string & buffer, T v)

Appends the shortest decimal representation of a numeric value to a buffer.

Template Parameters
T

The numeric type to format.

Parameters
buffer

The string to append to.

v

The value to format.

Returns

Nothing.

The value is formatted into a local buffer, which is then appended to buffer.

For long double, a platform-specific path is chosen:

  • On Windows and on platforms where long double has the same storage width as double (e.g. 32-bit Arm), the value is cast to double and formatted with std::to_chars.
  • On all other platforms (e.g. x86-64 Linux/macOS with 80-bit extended precision, or AArch64 and RISC-V with 128-bit quad precision), snprintf with %.*Lg is used as a portable fallback, because std::to_chars for long double may be unavailable (e.g. when linking with lld). The precision starts at LDBL_DIG and is increased until the text reads back, with strtold, as the same value, up to LDBL_DECIMAL_DIG, which always reads back exactly. This gives the shortest exact form, as std::to_chars gives for the other types; a shorter form would need fewer than LDBL_DIG digits, and Lg removes the trailing zeros, so starting at LDBL_DIG does not lose it. For zero and the subnormal values, which have fewer significant digits, the precision starts at 1. The value of errno, which strtold may set for subnormal values, is preserved. If no precision reads back exactly, the C library handles long double with the precision of double (e.g. newlib on AArch64 and RISC-V), and the value is cast to double and formatted with std::to_chars, as above.

For all other numeric types, std::to_chars is called directly, providing locale-independent formatting. The snprintf() path uses the decimal point of the C locale, which is . unless the application changes it with setlocale().

Definition at line 1348 of file detail-inlines.h.

1348 void
1349 append_number (std::string& buffer, const T v)
1350 {
1351 if constexpr (std::is_same_v<T, long double>)
1352 {
1353#if defined(_WIN32) \
1354 || (defined(__SIZEOF_LONG_DOUBLE__) \
1355 && __SIZEOF_LONG_DOUBLE__ == __SIZEOF_DOUBLE__)
1356 // On Windows (all toolchains: MinGW, Clang, MSVC), the C runtime
1357 // does not handle the %Lg printf specifier correctly for 80-bit
1358 // long double, producing garbage output. On platforms where long
1359 // double has the same width as double (e.g. 32-bit Arm), the cast
1360 // is lossless. In both cases, cast to double and use
1361 // std::to_chars.
1362 char buf[32];
1363 const auto [ptr, ec] = std::to_chars (buf, buf + sizeof (buf),
1364 static_cast<double> (v));
1365 if (ec == std::errc{})
1366 buffer.append (buf, ptr);
1367#else
1368 // On other platforms (80-bit extended precision on x86-64,
1369 // 128-bit quad precision on AArch64 and RISC-V), std::to_chars
1370 // for long double may be unavailable (e.g., with lld).
1371 // Use snprintf as a portable fallback; %Lg is supported by the C
1372 // libraries used on these platforms (e.g. glibc, the macOS libc,
1373 // and newlib, the latter with the precision of double on AArch64
1374 // and RISC-V). Use the smallest precision that reads back as the
1375 // same value; the buffer fits the 36 digits of the 128-bit
1376 // format, with the sign, the point, and the exponent.
1377 char buf[48];
1378 const int saved_errno = errno;
1379 // The subnormal values have fewer significant digits.
1380 const int min_precision
1381 = (v > -LDBL_MIN && v < LDBL_MIN) ? 1 : LDBL_DIG;
1382 bool is_exact = false;
1383 for (int precision = min_precision; precision <= LDBL_DECIMAL_DIG;
1384 ++precision)
1385 {
1386 // GCC 11 does not know the range of the precision, and
1387 // warns that the text may be truncated; it cannot be, since
1388 // the precision is at most LDBL_DECIMAL_DIG.
1389#if defined(__GNUC__) && !defined(__clang__)
1390#pragma GCC diagnostic push
1391#pragma GCC diagnostic ignored "-Wformat-truncation"
1392#endif // defined(__GNUC__) && !defined(__clang__)
1393 snprintf (buf, sizeof (buf), "%.*Lg", precision, v);
1394#if defined(__GNUC__) && !defined(__clang__)
1395#pragma GCC diagnostic pop
1396#endif // defined(__GNUC__) && !defined(__clang__)
1397 // A NaN is never equal to itself, and has no digits.
1398 if (v != v || strtold (buf, nullptr) == v)
1399 {
1400 is_exact = true;
1401 break;
1402 }
1403 }
1404 errno = saved_errno;
1405 if (is_exact)
1406 {
1407 buffer.append (buf);
1408 }
1409 else
1410 {
1411 // The C library formats or reads long double with the
1412 // precision of double (e.g. newlib on AArch64 and RISC-V),
1413 // and its long text would only show the error of the
1414 // conversion; use the shortest form of the double value,
1415 // as on the platforms above.
1416 const auto [ptr, ec] = std::to_chars (buf, buf + sizeof (buf),
1417 static_cast<double> (v));
1418 if (ec == std::errc{})
1419 buffer.append (buf, ptr);
1421#endif // long double formatting
1422 }
1423 else
1424 {
1425 char buf[32];
1426 const auto [ptr, ec] = std::to_chars (buf, buf + sizeof (buf), v);
1427 if (ec == std::errc{})
1428 buffer.append (buf, ptr);
1429 }

Reference micro_os_plus::micro_test_plus::detail::append_number.

Referenced by micro_os_plus::micro_test_plus::detail::expression_formatter::append_first_difference, micro_os_plus::micro_test_plus::detail::append_number, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<< and micro_os_plus::micro_test_plus::detail::reporter::operator<<.

append_pointer()​

template <class T>
void micro_os_plus::micro_test_plus::detail::append_pointer (std::string & buffer, T * v)

Appends the hexadecimal address of a pointer to a buffer.

Shared by the reporter and the expression_formatter, so that the pointers are displayed identically in the messages and in the expressions. Pointers to const or volatile objects, and function pointers, are accepted too.

Template Parameters
T

The type pointed to (an object or a function type).

Parameters
buffer

The string to append to.

v

The pointer.

Returns

Nothing.

Null pointers are rendered as "0x0", regardless of the platform, avoiding platform-specific representations such as "(nil)" on Linux/glibc. Non-null pointers are formatted with snprintf(), using the p specifier.

The p specifier expects a void*. Object pointers are converted implicitly to const volatile void*, which accepts any qualification, and the qualifiers are then removed with const_cast; a reinterpret_cast to void* would not compile for pointers to const objects. The object is not accessed. Function pointers, which do not convert to void* implicitly, use reinterpret_cast.

Definition at line 1454 of file detail-inlines.h.

1454 void
1455 append_pointer (std::string& buffer, T* v)
1456 {
1457 if (v == nullptr)
1458 {
1459 buffer.append ("0x0");
1460 return;
1461 }
1462
1463 void* p;
1464 if constexpr (std::is_function_v<T>)
1465 {
1466 p = reinterpret_cast<void*> (v);
1467 }
1468 else
1469 {
1470 const volatile void* cvp = v;
1471 p = const_cast<void*> (cvp);
1472 }
1473
1474#if defined(__GNUC__)
1475#pragma GCC diagnostic push
1476
1477#if defined(__clang__)
1478#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
1479#endif // defined(__clang__)
1480#endif // defined(__GNUC__)
1481
1482 char buf[20];
1483 snprintf (buf, sizeof (buf), "%p", p);
1484 buffer.append (buf);
1485
1486#if defined(__GNUC__)
1487#pragma GCC diagnostic pop
1488#endif // defined(__GNUC__)

Reference micro_os_plus::micro_test_plus::detail::append_pointer.

Referenced by micro_os_plus::micro_test_plus::detail::append_pointer, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<< and micro_os_plus::micro_test_plus::detail::reporter::operator<<.

as_comparison_integer()​

template <class T>
comparison_integer_t< T > micro_os_plus::micro_test_plus::detail::as_comparison_integer (const T value)
nodiscard constexpr noexcept

Converts an integer or a character to the type used to compare it.

The conversion keeps the value, since the type has the same size and signedness. Applied to both operands of the std::cmp_*() functions, which do not accept the character types.

Template Parameters
T

The type of the operand.

Parameters
value

The operand.

Returns

The value, as comparison_integer_t<T>.

Definition at line 343 of file detail-inlines.h.

343 as_comparison_integer (const T value) noexcept
344 {
345 return static_cast<comparison_integer_t<T>> (value);
346 }

Reference micro_os_plus::micro_test_plus::detail::as_comparison_integer.

Referenced by micro_os_plus::micro_test_plus::detail::equal_to::apply, micro_os_plus::micro_test_plus::detail::greater::apply, micro_os_plus::micro_test_plus::detail::greater_equal::apply, micro_os_plus::micro_test_plus::detail::less::apply, micro_os_plus::micro_test_plus::detail::less_equal::apply, micro_os_plus::micro_test_plus::detail::not_equal_to::apply and micro_os_plus::micro_test_plus::detail::as_comparison_integer.

as_operand()​

template <class T>
decltype(auto) micro_os_plus::micro_test_plus::detail::as_operand (T && t)
nodiscard constexpr noexcept

Prepares an operand of a comparator.

The arrays and the pointers of the character types (char, wchar_t, char8_t, char16_t, and char32_t, but not signed char or unsigned char, which are byte buffers) are converted to basic_c_string, so that they are compared by content; the other arrays, including those of volatile characters, are converted to array_operand, so that they are compared element by element, the functions to function pointers, and all other operands are returned unchanged, so the other pointers, including the pointers to volatile characters, and the functions are compared by address.

The other operands are forwarded, so that the comparator moves a temporary, and refers to a named operand.

Template Parameters
T

The type deduced for the forwarding reference.

Parameters
t

The operand.

Returns

A basic_c_string for the C strings, an array_operand for the other arrays, otherwise t, forwarded.

Arrays of the character types are converted using their size as a bound, pointers to them using the null terminator; the const qualifier of the characters is ignored. The volatile characters are not strings, since basic_c_string cannot refer to them: an array of them is converted to array_operand, like the other arrays, and compared element by element, and a pointer to them is forwarded, and compared by address, like the other pointers. The functions are converted to function pointers. Every other operand is forwarded, unchanged, as an lvalue or an rvalue reference.

The qualifiers of an array are those of its elements, so the element type is taken from the type of the argument, not from the type without qualifiers, which would lose the volatile of the elements, and the conversion to the pointer to const elements would not compile.

Definition at line 1725 of file detail-inlines.h.

1725 constexpr auto
1726 as_operand (T&& t) noexcept -> decltype (auto)
1727 {
1728 using type = std::remove_cvref_t<T>;
1729 // The cv-qualifiers of an array are those of its elements, which
1730 // std::remove_cvref_t removes; keep volatile for the element type.
1731 using element_type = std::remove_const_t<
1732 std::remove_extent_t<std::remove_reference_t<T>>>;
1733
1734 if constexpr (std::is_array_v<type> && character<element_type>
1735 && not std::is_volatile_v<element_type>)
1736 {
1738 t, std::extent_v<type>
1739 };
1740 }
1741 else if constexpr (character_pointer<type>)
1742 {
1744 t
1745 };
1746 }
1747 else if constexpr (std::is_array_v<type>)
1748 {
1749 // Other arrays (e.g. byte buffers) are compared by content.
1751 static_cast<const element_type*> (t), std::extent_v<type>
1752 };
1753 }
1754 else if constexpr (std::is_function_v<type>)
1755 {
1756 // Functions are compared by address, as function pointers.
1757 return &t;
1758 }
1759 else
1760 {
1761 return std::forward<T> (t);
1762 }

Reference micro_os_plus::micro_test_plus::detail::as_operand.

Referenced by micro_os_plus::micro_test_plus::detail::compare_op< Lhs_T, Rhs_T, Compare_T >::compare_op, micro_os_plus::micro_test_plus::detail::predicate_op< T, Predicate_T >::predicate_op, micro_os_plus::micro_test_plus::detail::as_operand, micro_os_plus::micro_test_plus::detail::compare_op, micro_os_plus::micro_test_plus::eq, micro_os_plus::micro_test_plus::ge, micro_os_plus::micro_test_plus::gt, micro_os_plus::micro_test_plus::le, micro_os_plus::micro_test_plus::lt and micro_os_plus::micro_test_plus::ne.

differ_by_at_most()​

template <class Lhs_T, class Rhs_T, class Epsilon_T>
bool micro_os_plus::micro_test_plus::detail::differ_by_at_most (Lhs_T lhs, Rhs_T rhs, Epsilon_T epsilon)
nodiscard constexpr

Tells whether two numbers differ by at most a tolerance.

If the three values are integers, the distance is computed exactly, as an unsigned value, also for operands of mixed signedness, and compared with the tolerance with std::cmp_less_equal(), so that neither overflows nor wraps around. Otherwise, the values are converted to their common floating-point type, and the absolute value of the difference is compared with the tolerance. A negative tolerance, or a NaN, makes the result false.

Template Parameters
Lhs_T

The arithmetic type of the first value.

Rhs_T

The arithmetic type of the second value.

Epsilon_T

The arithmetic type of the tolerance.

Parameters
lhs

The first value.

rhs

The second value.

epsilon

The tolerance.

Return Values
true

The distance between the values is less than or equal to the tolerance.

false

Otherwise.

For the integers, the larger value is selected with std::cmp_less(), and the distance is computed in std::uintmax_t: when both values have the same sign, as the modular difference of their conversions, which is exact; otherwise, as the sum of the larger value and of the magnitude of the negative one. A sum that does not fit in std::uintmax_t is flagged as an overflow, and gives false, since the true distance is larger than any tolerance. The tolerance is compared with std::cmp_less_equal(), so a negative one gives false.

For the other types, the values are converted to their common type, which is a floating-point one, so std::int64_t values compared with a float tolerance lose precision; the comparison is inclusive, so a distance equal to the tolerance succeeds. Two equal values, including two infinities of the same sign, whose difference would be NaN, are at the distance zero; a NaN operand always fails.

Definition at line 940 of file detail-inlines.h.

940 constexpr bool
941 differ_by_at_most (Lhs_T lhs, Rhs_T rhs, Epsilon_T epsilon)
942 {
943 if constexpr (std::is_integral_v<Lhs_T> and std::is_integral_v<Rhs_T>
944 and std::is_integral_v<Epsilon_T>)
945 {
946 bool overflow = false;
947 const auto distance
948 = [&overflow] (auto larger,
949 auto smaller) constexpr -> std::uintmax_t
950 {
951 if constexpr (std::is_signed_v<decltype (smaller)>)
952 {
953 if (smaller < 0 and std::cmp_greater_equal (larger, 0))
954 {
955 // The magnitude of the negative value, without
956 // negating the minimum of its type.
957 const auto magnitude
958 = static_cast<std::uintmax_t> (-(smaller + 1)) + 1u;
959 const auto upper = static_cast<std::uintmax_t> (larger);
960 if (upper > std::numeric_limits<std::uintmax_t>::max ()
961 - magnitude)
962 {
963 overflow = true;
964 return std::numeric_limits<std::uintmax_t>::max ();
965 }
966 return upper + magnitude;
967 }
968 }
969 return static_cast<std::uintmax_t> (larger)
970 - static_cast<std::uintmax_t> (smaller);
971 };
972
973 const std::uintmax_t difference = std::cmp_less (lhs, rhs)
974 ? distance (rhs, lhs)
975 : distance (lhs, rhs);
976 return !overflow && std::cmp_less_equal (difference, epsilon);
977 }
978 else
979 {
980 using float_type = std::common_type_t<Lhs_T, Rhs_T, Epsilon_T>;
981 const auto left = static_cast<float_type> (lhs);
982 const auto right = static_cast<float_type> (rhs);
983
984#if defined(__GNUC__)
985#pragma GCC diagnostic push
986#pragma GCC diagnostic ignored "-Wfloat-equal"
987#endif // defined(__GNUC__)
988 // The exact comparison is deliberate, for the infinities.
989 const float_type distance
990 = (left == right) ? float_type{} : math::abs (left - right);
991#if defined(__GNUC__)
992#pragma GCC diagnostic pop
993#endif // defined(__GNUC__)
994
995 return distance <= static_cast<float_type> (epsilon);
996 }

References micro_os_plus::micro_test_plus::detail::math::abs and micro_os_plus::micro_test_plus::detail::differ_by_at_most.

Referenced by micro_os_plus::micro_test_plus::detail::approx_op< Lhs_T, Rhs_T, Epsilon_T >::approx_op, micro_os_plus::micro_test_plus::detail::compare_op and micro_os_plus::micro_test_plus::detail::differ_by_at_most.

reject_compare_tag()​

template <class Tag_T>
void micro_os_plus::micro_test_plus::detail::reject_compare_tag (void)
constexpr noexcept

Stops the compilation of a compare_op whose tag is rejected, with a message that tells why.

Always fails the static assertion that applies, since it is called only when extension::compare_tag is not satisfied: the missing or unprintable symbol is reported first, then the apply() that cannot be called with the operands, or whose result is not convertible to bool.

Template Parameters
Tag_T

The tag of the comparator.

Parameters

None.

Returns

Nothing.

Definition at line 778 of file detail-inlines.h.

778 constexpr void
779 reject_compare_tag (void) noexcept
780 {
781 if constexpr (not tag_has_symbol<Tag_T>)
782 {
783 static_assert (not std::is_same_v<Tag_T, Tag_T>,
784 "the tag of a comparator needs a static member "
785 "symbol that can be written, such as a const "
786 "char*");
787 }
788 else
789 {
790 static_assert (not std::is_same_v<Tag_T, Tag_T>,
791 "the tag of a comparator needs a static member "
792 "function apply (lhs, rhs) that can be called with "
793 "the operands and returns a value convertible to "
794 "bool");
795 }

Reference micro_os_plus::micro_test_plus::detail::reject_compare_tag.

Referenced by micro_os_plus::micro_test_plus::detail::reject_compare_tag.

reject_comparison()​

template <comparison Op, class Lhs_T, class Rhs_T>
void micro_os_plus::micro_test_plus::detail::reject_comparison (void)
constexpr noexcept

Stops the compilation of a comparator whose operands are rejected, with a message that tells why.

Always fails the static assertion that applies, since it is called only when comparable_operands is not satisfied: an operand that cannot be written is reported first, then the operator that the operands do not support. Called by the overloads of the comparators that accept the rejected operands, so that the compiler shows this message instead of a list of the unsatisfied constraints.

Template Parameters
Op

The comparison of the comparator.

Lhs_T

The type of the left operand.

Rhs_T

The type of the right operand.

Parameters

None.

Returns

Nothing.

Definition at line 557 of file detail-inlines.h.

557 constexpr void
558 reject_comparison (void) noexcept
559 {
560 if constexpr (not extension::printable<Lhs_T>
562 {
563 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
564 "an operand of the comparator cannot be written in "
565 "the report; define an operator<< (mt::"
566 "expression_formatter&, const T&) in the namespace "
567 "of its type");
568 }
569 else if constexpr (Op == comparison::equal)
570 {
571 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
572 "the operands of eq () cannot be compared with ==");
573 }
574 else if constexpr (Op == comparison::not_equal)
575 {
576 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
577 "the operands of ne () cannot be compared with !=");
578 }
579 else if constexpr (Op == comparison::greater)
580 {
581 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
582 "the operands of gt () cannot be compared with >");
583 }
584 else if constexpr (Op == comparison::greater_equal)
585 {
586 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
587 "the operands of ge () cannot be compared with >=");
588 }
589 else if constexpr (Op == comparison::less)
590 {
591 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
592 "the operands of lt () cannot be compared with <");
593 }
594 else
595 {
596 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
597 "the operands of le () cannot be compared with <=");
598 }

References micro_os_plus::micro_test_plus::detail::equal, micro_os_plus::micro_test_plus::detail::greater, micro_os_plus::micro_test_plus::detail::greater_equal, micro_os_plus::micro_test_plus::detail::less, micro_os_plus::micro_test_plus::detail::not_equal and micro_os_plus::micro_test_plus::detail::reject_comparison.

Referenced by micro_os_plus::micro_test_plus::detail::reject_comparison.

reject_predicate_tag()​

template <class Tag_T>
void micro_os_plus::micro_test_plus::detail::reject_predicate_tag (void)
constexpr noexcept

Stops the compilation of a predicate_op whose tag is rejected, with a message that tells why.

As reject_compare_tag(), for the tag of a predicate, whose apply() takes one operand; called only when extension::predicate_tag is not satisfied.

Template Parameters
Tag_T

The tag of the predicate.

Parameters

None.

Returns

Nothing.

Definition at line 815 of file detail-inlines.h.

815 constexpr void
816 reject_predicate_tag (void) noexcept
817 {
818 if constexpr (not tag_has_symbol<Tag_T>)
819 {
820 static_assert (not std::is_same_v<Tag_T, Tag_T>,
821 "the tag of a predicate needs a static member "
822 "symbol that can be written, such as a const "
823 "char*");
824 }
825 else
826 {
827 static_assert (not std::is_same_v<Tag_T, Tag_T>,
828 "the tag of a predicate needs a static member "
829 "function apply (operand) that can be called with "
830 "the operand and returns a value convertible to "
831 "bool");
832 }

Reference micro_os_plus::micro_test_plus::detail::reject_predicate_tag.

Referenced by micro_os_plus::micro_test_plus::detail::reject_predicate_tag.

unwrap()​

template <class T>
decltype(auto) micro_os_plus::micro_test_plus::detail::unwrap (const T & t)
nodiscard constexpr

Returns the value wrapped by a framework type, or the operand itself.

If the type T is a framework expression type (see extension::is_op) that provides a get() member function, it is invoked and its result returned. Otherwise a reference to the argument itself is returned, so that the types of the application, even those with a get() member function, are compared with their own operators, without a copy. The selection is performed at compile time via if constexpr with an inline requires expression; the return type is deduced with decltype (auto) from the selected branch.

Always call it qualified, as detail::unwrap(), so that argument-dependent lookup cannot select a function of the application, defined in the namespace of an operand.

The operand is returned by reference, without a copy; the result is valid only as long as the operand.

Template Parameters
T

The type of the operand.

Parameters
t

The operand.

Returns

The value returned by t.get() for the framework wrappers (the literals and the to_* wrappers), otherwise a reference to t.

Definition at line 98 of file detail-inlines.h.

98 unwrap (const T& t)
99 {
100#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_DEPRECATED_ENABLED)
101 if constexpr (extension::is_op<T> && requires { t.get (); })
102 return t.get ();
103 else
104#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_DEPRECATED_ENABLED)
105 return t;
106 }

Reference micro_os_plus::micro_test_plus::detail::unwrap.

Referenced by micro_os_plus::micro_test_plus::detail::approx_op< Lhs_T, Rhs_T, Epsilon_T >::approx_op, micro_os_plus::micro_test_plus::detail::compare_op< Lhs_T, Rhs_T, Compare_T >::compare_op, micro_os_plus::micro_test_plus::detail::predicate_op< T, Predicate_T >::predicate_op, micro_os_plus::micro_test_plus::detail::approx_op< Lhs_T, Rhs_T, Epsilon_T >::epsilon, micro_os_plus::micro_test_plus::detail::binary_op< Lhs_T, Rhs_T >::lhs, micro_os_plus::micro_test_plus::detail::unary_op< T >::operand, micro_os_plus::micro_test_plus::detail::expression_formatter::operator<<, micro_os_plus::micro_test_plus::detail::binary_op< Lhs_T, Rhs_T >::rhs, micro_os_plus::micro_test_plus::detail::unwrap and micro_os_plus::micro_test_plus::detail::within_tolerance.

File Listing​

The file content with the documentation metadata removed is:

1/*
2 * This file is part of the µOS++ project (https://micro-os-plus.github.io/).
3 * Copyright (c) 2021-2026 Liviu Ionescu. All rights reserved.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose is hereby granted, under the terms of the MIT license.
7 *
8 * If a copy of the license was not distributed with this file, it can be
9 * obtained from https://opensource.org/licenses/mit.
10 *
11 * Major parts of the code are inspired from v1.1.8 of the Boost UT project,
12 * released under the terms of the Boost Version 1.0 Software License,
13 * which can be obtained from https://www.boost.org/LICENSE_1_0.txt.
14 */
15
16// ----------------------------------------------------------------------------
17
44
45#ifndef MICRO_OS_PLUS_MICRO_TEST_PLUS_INLINES_DETAIL_INLINES_H_
46#define MICRO_OS_PLUS_MICRO_TEST_PLUS_INLINES_DETAIL_INLINES_H_
47
48// ----------------------------------------------------------------------------
49
50#if defined(__cplusplus)
51
52// ----------------------------------------------------------------------------
53
54#include <algorithm>
55#include <cerrno>
56#include <cfloat>
57#include <charconv>
58#include <cstdint>
59#include <cstdio>
60#include <cstdlib>
61#include <exception>
62#include <limits>
63
64// ----------------------------------------------------------------------------
65
66#if defined(__GNUC__)
67#pragma GCC diagnostic push
68
69#pragma GCC diagnostic ignored "-Waggregate-return"
70#if defined(__clang__)
71#pragma clang diagnostic ignored "-Wunknown-warning-option"
72#pragma clang diagnostic ignored "-Wc++98-compat"
73#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
74#endif // defined(__clang__)
75#endif // defined(__GNUC__)
76
77// ============================================================================
78
80{
81 namespace detail
82 {
83 // ========================================================================
84
96 template <class T>
97 constexpr decltype (auto)
98 unwrap (const T& t)
99 {
100#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_DEPRECATED_ENABLED)
101 if constexpr (extension::is_op<T> && requires { t.get (); })
102 return t.get ();
103 else
104#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_DEPRECATED_ENABLED)
105 return t;
106 }
107
108 // ------------------------------------------------------------------------
109
115 template <class T>
116 template <class U>
117 constexpr unary_op<T>::unary_op (U&& t, bool value)
118 : t_{ std::forward<U> (t) }, value_{ value }
119 {
120 }
121
127 template <class T>
128 constexpr unary_op<T>::
129 operator bool () const
130 {
131 return value_;
132 }
133
139 template <class T>
140 constexpr decltype (auto)
142 {
143 return detail::unwrap (t_);
144 }
145
146 // ------------------------------------------------------------------------
147
153 template <class Lhs_T, class Rhs_T>
154 template <class L, class R>
156 : lhs_{ std::forward<L> (lhs) }, rhs_{ std::forward<R> (rhs) },
157 value_{ value }
158 {
159 }
160
166 template <class Lhs_T, class Rhs_T>
168 operator bool () const
169 {
170 return value_;
171 }
172
179 template <class Lhs_T, class Rhs_T>
180 constexpr decltype (auto)
182 {
183 return detail::unwrap (lhs_);
184 }
185
192 template <class Lhs_T, class Rhs_T>
193 constexpr decltype (auto)
195 {
196 return detail::unwrap (rhs_);
197 }
198
199 // ------------------------------------------------------------------------
200
201#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_DEPRECATED_ENABLED)
209 template <class Lhs_T, class Rhs_T>
211 constexpr bool
212 within_tolerance (const Lhs_T& lhs, const Rhs_T& rhs)
213 {
214#if defined(__GNUC__)
215#pragma GCC diagnostic push
216#pragma GCC diagnostic ignored "-Wfloat-equal"
217#pragma GCC diagnostic ignored "-Wconversion"
218#pragma GCC diagnostic ignored "-Wdouble-promotion"
219#pragma GCC diagnostic ignored "-Wsign-compare"
220#if defined(__clang__)
221#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"
222#pragma clang diagnostic ignored "-Wpedantic"
223#endif // defined(__clang__)
224#endif // defined(__GNUC__)
225 const auto difference
226 = math::abs (detail::unwrap (lhs) - detail::unwrap (rhs));
229 {
230 // If both values have precision, use the smallest one.
231 return difference < math::min_value (lhs.epsilon, rhs.epsilon);
232 }
233 else if constexpr (extension::has_epsilon<Lhs_T>)
234 {
235 return difference < lhs.epsilon;
236 }
237 else
238 {
239 return difference < rhs.epsilon;
240 }
241#if defined(__GNUC__)
242#pragma GCC diagnostic pop
243#endif // defined(__GNUC__)
244 }
245#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_DEPRECATED_ENABLED)
246
247 // ------------------------------------------------------------------------
248
249 // The operands are compared as they are, including mixed integer and
250 // floating-point types (integers of different signedness are compared
251 // by value, see `sign_mixed_integers`); the warnings would be
252 // reported in the test code of the application, not in the library.
253#if defined(__GNUC__)
254#pragma GCC diagnostic push
255#pragma GCC diagnostic ignored "-Wfloat-equal"
256#pragma GCC diagnostic ignored "-Wconversion"
257#pragma GCC diagnostic ignored "-Wdouble-promotion"
258#pragma GCC diagnostic ignored "-Wsign-compare"
259#if defined(__clang__)
260#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"
261#pragma clang diagnostic ignored "-Wpedantic"
262#endif // defined(__clang__)
263#endif // defined(__GNUC__)
264
278 template <class T>
280 {
287 using type = T;
288 };
289
299 template <class T>
300 requires character<T>
302 {
311 using type
312 = std::conditional_t<std::is_signed_v<T>, std::make_signed_t<T>,
313 std::make_unsigned_t<T>>;
314 };
315
325 template <class T>
327
341 template <class T>
342 [[nodiscard]] constexpr comparison_integer_t<T>
343 as_comparison_integer (const T value) noexcept
344 {
345 return static_cast<comparison_integer_t<T>> (value);
346 }
347
367 template <class Lhs_T, class Rhs_T>
369 = std::is_integral_v<Lhs_T> and std::is_integral_v<Rhs_T>
370 and (not std::is_same_v<Lhs_T, bool>)
371 and (not std::is_same_v<Rhs_T, bool>)
372 and (std::is_signed_v<comparison_integer_t<Lhs_T>>
373 != std::is_signed_v<comparison_integer_t<Rhs_T>>);
374
385 template <class T>
388
402 template <class Lhs_T, class Rhs_T>
404 = std::is_pointer_v<Lhs_T> and std::is_pointer_v<Rhs_T>
405 and (std::is_function_v<std::remove_pointer_t<Lhs_T>>
406 or std::is_function_v<std::remove_pointer_t<Rhs_T>>);
407
417 enum class comparison : unsigned char
418 {
420 equal,
424 greater,
428 less,
431 };
432
444 namespace comparison_lookup
445 {
446 using std::operator==;
447 using std::operator!=;
448 using std::operator<;
449 using std::operator>;
450 using std::operator<=;
451 using std::operator>=;
452
466 template <comparison Op, class Lhs_T, class Rhs_T>
467 concept can_compare
468 = (Op == comparison::equal
469 and requires (const Lhs_T& lhs, const Rhs_T& rhs) { lhs == rhs; })
470 or (Op == comparison::not_equal
471 and requires (const Lhs_T& lhs,
472 const Rhs_T& rhs) { lhs != rhs; })
473 or (Op == comparison::greater
474 and requires (const Lhs_T& lhs,
475 const Rhs_T& rhs) { lhs > rhs; })
477 and requires (const Lhs_T& lhs,
478 const Rhs_T& rhs) { lhs >= rhs; })
479 or (Op == comparison::less
480 and requires (const Lhs_T& lhs,
481 const Rhs_T& rhs) { lhs < rhs; })
483 and requires (const Lhs_T& lhs, const Rhs_T& rhs) {
484 lhs <= rhs;
485 });
486 } // namespace comparison_lookup
487
507 template <comparison Op, class Lhs_T, class Rhs_T>
508 concept comparable
515
530 template <comparison Op, class Lhs_T, class Rhs_T>
534
556 template <comparison Op, class Lhs_T, class Rhs_T>
557 constexpr void
558 reject_comparison (void) noexcept
559 {
560 if constexpr (not extension::printable<Lhs_T>
562 {
563 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
564 "an operand of the comparator cannot be written in "
565 "the report; define an operator<< (mt::"
566 "expression_formatter&, const T&) in the namespace "
567 "of its type");
568 }
569 else if constexpr (Op == comparison::equal)
570 {
571 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
572 "the operands of eq () cannot be compared with ==");
573 }
574 else if constexpr (Op == comparison::not_equal)
575 {
576 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
577 "the operands of ne () cannot be compared with !=");
578 }
579 else if constexpr (Op == comparison::greater)
580 {
581 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
582 "the operands of gt () cannot be compared with >");
583 }
584 else if constexpr (Op == comparison::greater_equal)
585 {
586 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
587 "the operands of ge () cannot be compared with >=");
588 }
589 else if constexpr (Op == comparison::less)
590 {
591 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
592 "the operands of lt () cannot be compared with <");
593 }
594 else
595 {
596 static_assert (not std::is_same_v<Lhs_T, Lhs_T>,
597 "the operands of le () cannot be compared with <=");
598 }
599 }
600
607 template <class Lhs_T, class Rhs_T>
608 constexpr bool
609 equal_to::apply (const Lhs_T& lhs, const Rhs_T& rhs)
610 {
611 using std::operator==;
613 {
614 return std::cmp_equal (as_comparison_integer (lhs),
616 }
617 else
618 {
619 return lhs == rhs;
620 }
621 }
622
630 template <class Lhs_T, class Rhs_T>
631 constexpr bool
632 not_equal_to::apply (const Lhs_T& lhs, const Rhs_T& rhs)
633 {
634 using std::operator==;
635 using std::operator!=;
637 {
638 return std::cmp_not_equal (as_comparison_integer (lhs),
640 }
641 else
642 {
643 return lhs != rhs;
644 }
645 }
646
653 template <class Lhs_T, class Rhs_T>
654 constexpr bool
655 greater::apply (const Lhs_T& lhs, const Rhs_T& rhs)
656 {
657 using std::operator>;
659 {
660 return std::cmp_greater (as_comparison_integer (lhs),
662 }
663 else
664 {
665 return lhs > rhs;
666 }
667 }
668
675 template <class Lhs_T, class Rhs_T>
676 constexpr bool
677 greater_equal::apply (const Lhs_T& lhs, const Rhs_T& rhs)
678 {
679 using std::operator>=;
681 {
682 return std::cmp_greater_equal (as_comparison_integer (lhs),
684 }
685 else
686 {
687 return lhs >= rhs;
688 }
689 }
690
697 template <class Lhs_T, class Rhs_T>
698 constexpr bool
699 less::apply (const Lhs_T& lhs, const Rhs_T& rhs)
700 {
701 using std::operator<;
703 {
704 return std::cmp_less (as_comparison_integer (lhs),
706 }
707 else
708 {
709 return lhs < rhs;
710 }
711 }
712
719 template <class Lhs_T, class Rhs_T>
720 constexpr bool
721 less_equal::apply (const Lhs_T& lhs, const Rhs_T& rhs)
722 {
723 using std::operator<=;
725 {
726 return std::cmp_less_equal (as_comparison_integer (lhs),
728 }
729 else
730 {
731 return lhs <= rhs;
732 }
733 }
734
735#if defined(__GNUC__)
736#pragma GCC diagnostic pop
737#endif // defined(__GNUC__)
738
739 // ------------------------------------------------------------------------
740
753 template <class Tag_T>
754 concept tag_has_symbol = requires {
755 requires extension::printable<
756 std::remove_cvref_t<decltype (Tag_T::symbol)>>;
757 };
758
777 template <class Tag_T>
778 constexpr void
779 reject_compare_tag (void) noexcept
780 {
781 if constexpr (not tag_has_symbol<Tag_T>)
782 {
783 static_assert (not std::is_same_v<Tag_T, Tag_T>,
784 "the tag of a comparator needs a static member "
785 "symbol that can be written, such as a const "
786 "char*");
787 }
788 else
789 {
790 static_assert (not std::is_same_v<Tag_T, Tag_T>,
791 "the tag of a comparator needs a static member "
792 "function apply (lhs, rhs) that can be called with "
793 "the operands and returns a value convertible to "
794 "bool");
795 }
796 }
797
814 template <class Tag_T>
815 constexpr void
816 reject_predicate_tag (void) noexcept
817 {
818 if constexpr (not tag_has_symbol<Tag_T>)
819 {
820 static_assert (not std::is_same_v<Tag_T, Tag_T>,
821 "the tag of a predicate needs a static member "
822 "symbol that can be written, such as a const "
823 "char*");
824 }
825 else
826 {
827 static_assert (not std::is_same_v<Tag_T, Tag_T>,
828 "the tag of a predicate needs a static member "
829 "function apply (operand) that can be called with "
830 "the operand and returns a value convertible to "
831 "bool");
832 }
833 }
834
852 template <class Lhs_T, class Rhs_T, class Compare_T>
853 template <class L, class R>
857 R&& rhs,
858 Compare_T)
859 : binary_op<Lhs_T, Rhs_T>{ detail::as_operand (std::forward<L> (lhs)),
860 detail::as_operand (std::forward<R> (rhs)),
861 [&] () -> bool
862 {
863 // The result is computed before the arguments are moved, since
864 // the base constructor is entered only after all its arguments
865 // are evaluated.
866#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_DEPRECATED_ENABLED)
867 using lhs_type = std::remove_cvref_t<L>;
868 using rhs_type = std::remove_cvref_t<R>;
869
870 if constexpr (detail::has_value<lhs_type>
872 {
873 return Compare_T::apply (lhs_type::value, rhs_type::value);
874 }
875 else if constexpr ((extension::has_epsilon<lhs_type>
877 and requires {
878 Compare_T::within_tolerance_result;
879 })
880 {
882 ? Compare_T::within_tolerance_result
883 : Compare_T::apply (detail::unwrap (lhs),
885 }
886 else
887#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_DEPRECATED_ENABLED)
888 {
889 return Compare_T::apply (detail::unwrap (lhs),
891 }
892 }() }
893 {
894 }
895
896#if !defined(__DOXYGEN__)
903 template <class Lhs_T, class Rhs_T, class Compare_T>
904 template <class L, class R>
908 R&& rhs,
909 Compare_T)
910 : binary_op<Lhs_T, Rhs_T>{ detail::as_operand (std::forward<L> (lhs)),
911 detail::as_operand (std::forward<R> (rhs)),
912 false }
913 {
915 }
916#endif // !defined(__DOXYGEN__)
917
918 // ------------------------------------------------------------------------
919
939 template <class Lhs_T, class Rhs_T, class Epsilon_T>
940 constexpr bool
941 differ_by_at_most (Lhs_T lhs, Rhs_T rhs, Epsilon_T epsilon)
942 {
943 if constexpr (std::is_integral_v<Lhs_T> and std::is_integral_v<Rhs_T>
944 and std::is_integral_v<Epsilon_T>)
945 {
946 bool overflow = false;
947 const auto distance
948 = [&overflow] (auto larger,
949 auto smaller) constexpr -> std::uintmax_t
950 {
951 if constexpr (std::is_signed_v<decltype (smaller)>)
952 {
953 if (smaller < 0 and std::cmp_greater_equal (larger, 0))
954 {
955 // The magnitude of the negative value, without
956 // negating the minimum of its type.
957 const auto magnitude
958 = static_cast<std::uintmax_t> (-(smaller + 1)) + 1u;
959 const auto upper = static_cast<std::uintmax_t> (larger);
960 if (upper > std::numeric_limits<std::uintmax_t>::max ()
961 - magnitude)
962 {
963 overflow = true;
964 return std::numeric_limits<std::uintmax_t>::max ();
965 }
966 return upper + magnitude;
967 }
968 }
969 return static_cast<std::uintmax_t> (larger)
970 - static_cast<std::uintmax_t> (smaller);
971 };
972
973 const std::uintmax_t difference = std::cmp_less (lhs, rhs)
974 ? distance (rhs, lhs)
975 : distance (lhs, rhs);
976 return !overflow && std::cmp_less_equal (difference, epsilon);
977 }
978 else
979 {
980 using float_type = std::common_type_t<Lhs_T, Rhs_T, Epsilon_T>;
981 const auto left = static_cast<float_type> (lhs);
982 const auto right = static_cast<float_type> (rhs);
983
984#if defined(__GNUC__)
985#pragma GCC diagnostic push
986#pragma GCC diagnostic ignored "-Wfloat-equal"
987#endif // defined(__GNUC__)
988 // The exact comparison is deliberate, for the infinities.
989 const float_type distance
990 = (left == right) ? float_type{} : math::abs (left - right);
991#if defined(__GNUC__)
992#pragma GCC diagnostic pop
993#endif // defined(__GNUC__)
994
995 return distance <= static_cast<float_type> (epsilon);
996 }
997 }
998
999 // ------------------------------------------------------------------------
1000
1007 template <class Lhs_T, class Rhs_T, class Epsilon_T>
1008 template <class L, class R, class E>
1010 E&& epsilon)
1011 : binary_op<Lhs_T, Rhs_T>{ std::forward<L> (lhs),
1012 std::forward<R> (rhs),
1017 epsilon_{ std::forward<E> (epsilon) }
1018 {
1019 }
1020
1025 template <class Lhs_T, class Rhs_T, class Epsilon_T>
1026 constexpr decltype (auto)
1028 {
1029 return detail::unwrap (epsilon_);
1030 }
1031
1032 // ------------------------------------------------------------------------
1033
1040 template <class Lhs_T, class Rhs_T>
1041 template <class L, class R>
1042 constexpr and_op<Lhs_T, Rhs_T>::and_op (L&& lhs, R&& rhs)
1043 : binary_op<Lhs_T, Rhs_T>{ std::forward<L> (lhs),
1044 std::forward<R> (rhs),
1045 static_cast<bool> (lhs)
1046 and static_cast<bool> (rhs) }
1047 {
1048 }
1049
1050 // ------------------------------------------------------------------------
1051
1058 template <class Lhs_T, class Rhs_T>
1059 template <class L, class R>
1060 constexpr or_op<Lhs_T, Rhs_T>::or_op (L&& lhs, R&& rhs)
1061 : binary_op<Lhs_T, Rhs_T>{ std::forward<L> (lhs),
1062 std::forward<R> (rhs),
1063 static_cast<bool> (lhs)
1064 or static_cast<bool> (rhs) }
1065 {
1066 }
1067
1068 // ------------------------------------------------------------------------
1069
1075 template <class T>
1076 template <class U>
1077 requires (not std::is_same_v<std::remove_cvref_t<U>, not_op<T>>)
1078 constexpr not_op<T>::not_op (U&& t)
1079 : unary_op<T>{ std::forward<U> (t), not static_cast<bool> (t) }
1080 {
1081 }
1082
1083 // ------------------------------------------------------------------------
1084
1091 template <class T, class Predicate_T>
1092 template <class U>
1094 constexpr predicate_op<T, Predicate_T>::predicate_op (U&& t, Predicate_T)
1095 : unary_op<T>{ detail::as_operand (std::forward<U> (t)),
1096 static_cast<bool> (
1097 Predicate_T::apply (detail::unwrap (t))) }
1098 {
1099 }
1100
1101#if !defined(__DOXYGEN__)
1107 template <class T, class Predicate_T>
1108 template <class U>
1110 constexpr predicate_op<T, Predicate_T>::predicate_op (U&& t, Predicate_T)
1111 : unary_op<T>{ detail::as_operand (std::forward<U> (t)), false }
1112 {
1114 }
1115#endif // !defined(__DOXYGEN__)
1116
1117 // ------------------------------------------------------------------------
1118
1119#if defined(__cpp_exceptions)
1120
1127 : value_{ result.value }, outcome_{ result.outcome },
1128 what_{ std::move (result.what) }
1129 {
1130 }
1131
1137 constexpr callable_op::
1138 operator bool () const
1139 {
1140 return value_;
1141 }
1142
1148 callable_op::outcome (void) const noexcept
1149 {
1150 return outcome_;
1151 }
1152
1157 inline const std::string&
1158 callable_op::what (void) const noexcept
1159 {
1160 return what_;
1161 }
1162
1163 // ------------------------------------------------------------------------
1164
1179 // When `Exception_T` is `std::exception`, or one of its bases, the
1180 // handler of `std::exception` is unreachable, which is not an error.
1181#if defined(__GNUC__)
1182#pragma GCC diagnostic push
1183#pragma GCC diagnostic ignored "-Wexceptions"
1184#endif // defined(__GNUC__)
1185
1186 template <class Exception_T>
1187 template <class Callable_T>
1188 requires std::invocable<Callable_T&>
1189 throws_op<Exception_T>::throws_op (Callable_T&& func)
1190 : callable_op{ [&func] () -> callable_result
1191 {
1192 try
1193 {
1194 func ();
1195 }
1196 catch (const failed_requirement&)
1197 {
1198 throw;
1199 }
1200 catch (const Exception_T&)
1201 {
1202 return { true, throw_outcome::none, {} };
1203 }
1204 catch (const std::exception& e)
1205 {
1206 return { false, throw_outcome::std_exception,
1207 e.what () != nullptr ? e.what () : "" };
1208 }
1209 catch (...)
1210 {
1211 return { false, throw_outcome::unknown_exception, {} };
1212 }
1213 return { false, throw_outcome::none, {} };
1214 }() }
1215 {
1216 }
1217
1218#if defined(__GNUC__)
1219#pragma GCC diagnostic pop
1220#endif // defined(__GNUC__)
1221
1222 // ------------------------------------------------------------------------
1223
1235 template <class Callable_T>
1236 requires std::invocable<Callable_T&>
1237 throws_op<void>::throws_op (Callable_T&& func)
1238 : callable_op{ [&func] () -> callable_result
1239 {
1240 try
1241 {
1242 func ();
1243 }
1244 catch (const failed_requirement&)
1245 {
1246 throw;
1247 }
1248 catch (...)
1249 {
1250 return { true, throw_outcome::none, {} };
1251 }
1252 return { false, throw_outcome::none, {} };
1253 }() }
1254 {
1255 }
1256
1257 // ------------------------------------------------------------------------
1258
1272 template <class Callable_T>
1273 requires std::invocable<Callable_T&>
1274 nothrow_op::nothrow_op (Callable_T&& func)
1275 : callable_op{ [&func] () -> callable_result
1276 {
1277 try
1278 {
1279 func ();
1280 }
1281 catch (const failed_requirement&)
1282 {
1283 throw;
1284 }
1285 catch (const std::exception& e)
1286 {
1287 return { false, throw_outcome::std_exception,
1288 e.what () != nullptr ? e.what () : "" };
1289 }
1290 catch (...)
1291 {
1292 return { false, throw_outcome::unknown_exception, {} };
1293 }
1294 return { true, throw_outcome::none, {} };
1295 }() }
1296 {
1297 }
1298
1299#endif // defined(__cpp_exceptions)
1300
1301 // ------------------------------------------------------------------------
1302
1303#if defined(__GNUC__)
1304#pragma GCC diagnostic push
1305
1306#pragma GCC diagnostic ignored "-Wfloat-equal"
1307#if defined(__clang__)
1308#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
1309#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
1310#endif // defined(__clang__)
1311#endif // defined(__GNUC__)
1312
1346 template <class T>
1347 requires std::is_arithmetic_v<T>
1348 void
1349 append_number (std::string& buffer, const T v)
1350 {
1351 if constexpr (std::is_same_v<T, long double>)
1352 {
1353#if defined(_WIN32) \
1354 || (defined(__SIZEOF_LONG_DOUBLE__) \
1355 && __SIZEOF_LONG_DOUBLE__ == __SIZEOF_DOUBLE__)
1356 // On Windows (all toolchains: MinGW, Clang, MSVC), the C runtime
1357 // does not handle the %Lg printf specifier correctly for 80-bit
1358 // long double, producing garbage output. On platforms where long
1359 // double has the same width as double (e.g. 32-bit Arm), the cast
1360 // is lossless. In both cases, cast to double and use
1361 // std::to_chars.
1362 char buf[32];
1363 const auto [ptr, ec] = std::to_chars (buf, buf + sizeof (buf),
1364 static_cast<double> (v));
1365 if (ec == std::errc{})
1366 buffer.append (buf, ptr);
1367#else
1368 // On other platforms (80-bit extended precision on x86-64,
1369 // 128-bit quad precision on AArch64 and RISC-V), std::to_chars
1370 // for long double may be unavailable (e.g., with lld).
1371 // Use snprintf as a portable fallback; %Lg is supported by the C
1372 // libraries used on these platforms (e.g. glibc, the macOS libc,
1373 // and newlib, the latter with the precision of double on AArch64
1374 // and RISC-V). Use the smallest precision that reads back as the
1375 // same value; the buffer fits the 36 digits of the 128-bit
1376 // format, with the sign, the point, and the exponent.
1377 char buf[48];
1378 const int saved_errno = errno;
1379 // The subnormal values have fewer significant digits.
1380 const int min_precision
1381 = (v > -LDBL_MIN && v < LDBL_MIN) ? 1 : LDBL_DIG;
1382 bool is_exact = false;
1383 for (int precision = min_precision; precision <= LDBL_DECIMAL_DIG;
1384 ++precision)
1385 {
1386 // GCC 11 does not know the range of the precision, and
1387 // warns that the text may be truncated; it cannot be, since
1388 // the precision is at most LDBL_DECIMAL_DIG.
1389#if defined(__GNUC__) && !defined(__clang__)
1390#pragma GCC diagnostic push
1391#pragma GCC diagnostic ignored "-Wformat-truncation"
1392#endif // defined(__GNUC__) && !defined(__clang__)
1393 snprintf (buf, sizeof (buf), "%.*Lg", precision, v);
1394#if defined(__GNUC__) && !defined(__clang__)
1395#pragma GCC diagnostic pop
1396#endif // defined(__GNUC__) && !defined(__clang__)
1397 // A NaN is never equal to itself, and has no digits.
1398 if (v != v || strtold (buf, nullptr) == v)
1399 {
1400 is_exact = true;
1401 break;
1402 }
1403 }
1404 errno = saved_errno;
1405 if (is_exact)
1406 {
1407 buffer.append (buf);
1408 }
1409 else
1410 {
1411 // The C library formats or reads long double with the
1412 // precision of double (e.g. newlib on AArch64 and RISC-V),
1413 // and its long text would only show the error of the
1414 // conversion; use the shortest form of the double value,
1415 // as on the platforms above.
1416 const auto [ptr, ec] = std::to_chars (buf, buf + sizeof (buf),
1417 static_cast<double> (v));
1418 if (ec == std::errc{})
1419 buffer.append (buf, ptr);
1421#endif // long double formatting
1422 }
1423 else
1424 {
1425 char buf[32];
1426 const auto [ptr, ec] = std::to_chars (buf, buf + sizeof (buf), v);
1427 if (ec == std::errc{})
1428 buffer.append (buf, ptr);
1429 }
1430 }
1431
1432#if defined(__GNUC__)
1433#pragma GCC diagnostic pop
1434#endif // defined(__GNUC__)
1435
1436 // ------------------------------------------------------------------------
1437
1453 template <class T>
1454 void
1455 append_pointer (std::string& buffer, T* v)
1456 {
1457 if (v == nullptr)
1458 {
1459 buffer.append ("0x0");
1460 return;
1461 }
1462
1463 void* p;
1464 if constexpr (std::is_function_v<T>)
1465 {
1466 p = reinterpret_cast<void*> (v);
1467 }
1468 else
1469 {
1470 const volatile void* cvp = v;
1471 p = const_cast<void*> (cvp);
1472 }
1473
1474#if defined(__GNUC__)
1475#pragma GCC diagnostic push
1476
1477#if defined(__clang__)
1478#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
1479#endif // defined(__clang__)
1480#endif // defined(__GNUC__)
1481
1482 char buf[20];
1483 snprintf (buf, sizeof (buf), "%p", p);
1484 buffer.append (buf);
1485
1486#if defined(__GNUC__)
1487#pragma GCC diagnostic pop
1488#endif // defined(__GNUC__)
1489 }
1490
1491 // ========================================================================
1492
1498 template <character Char_T>
1500 const Char_T* str) noexcept
1501 : str_{ str },
1502 size_{ str == nullptr ? 0 : std::char_traits<Char_T>::length (str) }
1503 {
1504 }
1505
1511 template <character Char_T>
1513 const Char_T* str, std::size_t capacity) noexcept
1514 : str_{ str }, size_{ capacity }
1515 {
1516 // Search within the array only; the view avoids indexing the raw
1517 // pointer.
1518 const std::size_t terminator
1519 = std::basic_string_view<Char_T>{ str, capacity }.find (Char_T{});
1520 if (terminator != std::basic_string_view<Char_T>::npos)
1521 {
1522 size_ = terminator;
1523 }
1524 }
1525
1530 template <character Char_T>
1531 constexpr std::basic_string_view<Char_T>
1532 basic_c_string<Char_T>::view (void) const noexcept
1533 {
1534 return str_ == nullptr ? std::basic_string_view<Char_T>{}
1535 : std::basic_string_view<Char_T>{ str_, size_ };
1536 }
1537
1542 template <character Char_T>
1543 constexpr bool
1544 basic_c_string<Char_T>::is_null (void) const noexcept
1545 {
1546 return str_ == nullptr;
1547 }
1548
1554 template <character Char_T>
1555 constexpr bool
1557 const basic_c_string<Char_T>& rhs) noexcept
1558 {
1559 if (lhs.is_null () || rhs.is_null ())
1560 {
1561 return lhs.is_null () && rhs.is_null ();
1562 }
1563 return lhs.view () == rhs.view ();
1564 }
1565
1570 template <character Char_T>
1571 constexpr bool
1573 const basic_c_string<Char_T>& lhs,
1574 std::type_identity_t<std::basic_string_view<Char_T>> rhs) noexcept
1575 {
1576 return !lhs.is_null () && lhs.view () == rhs;
1577 }
1578
1583 template <character Char_T>
1584 constexpr bool
1585 operator==(const basic_c_string<Char_T>& lhs, std::nullptr_t) noexcept
1586 {
1587 return lhs.is_null ();
1588 }
1589
1594 template <character Char_T>
1595 constexpr std::strong_ordering
1597 const basic_c_string<Char_T>& rhs) noexcept
1598 {
1599 if (lhs.is_null () || rhs.is_null ())
1600 {
1601 return !lhs.is_null () <=> !rhs.is_null ();
1602 }
1603 return lhs.view () <=> rhs.view ();
1604 }
1605
1610 template <character Char_T>
1611 constexpr std::strong_ordering
1613 const basic_c_string<Char_T>& lhs,
1614 std::type_identity_t<std::basic_string_view<Char_T>> rhs) noexcept
1615 {
1616 if (lhs.is_null ())
1617 {
1618 return std::strong_ordering::less;
1619 }
1620 return lhs.view () <=> rhs;
1621 }
1622
1627 template <class T>
1628 constexpr array_operand<T>::array_operand (const T* first,
1629 std::size_t count) noexcept
1630 : first_{ first }, count_{ count }
1631 {
1632 }
1633
1638 template <class T>
1639 constexpr const T*
1640 array_operand<T>::begin (void) const noexcept
1641 {
1642 return first_;
1643 }
1644
1645 // ------------------------------------------------------------------------
1646#if defined(__GNUC__)
1647#pragma GCC diagnostic push
1648#if defined(__clang__)
1649#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
1650#endif // defined(__clang__)
1651#endif // defined(__GNUC__)
1652
1657 template <class T>
1658 constexpr const T*
1659 array_operand<T>::end (void) const noexcept
1660 {
1661 return first_ + count_;
1662 }
1663
1664#if defined(__GNUC__)
1665#pragma GCC diagnostic pop
1666#endif // defined(__GNUC__)
1667 // ------------------------------------------------------------------------
1668
1677 template <class T, class R>
1678 requires (container_like<R>
1679 and std::equality_comparable_with<
1680 T, typename range_element<R>::type>)
1681 constexpr bool
1682 operator== (const array_operand<T>& lhs, const R& rhs)
1683 {
1684 return std::equal (lhs.begin (), lhs.end (), rhs.begin (), rhs.end (),
1685 [] (const auto& l, const auto& r)
1686 { return equal_to::apply (l, r); });
1687 }
1688
1693 template <class T, class R>
1694 requires (container_like<R>
1695 and std::three_way_comparable_with<
1697 constexpr auto
1698 operator<=> (const array_operand<T>& lhs, const R& rhs)
1699 {
1700 return std::lexicographical_compare_three_way (lhs.begin (), lhs.end (),
1701 rhs.begin (), rhs.end ());
1702 }
1703
1704 // ------------------------------------------------------------------------
1705
1724 template <class T>
1725 constexpr auto
1726 as_operand (T&& t) noexcept -> decltype (auto)
1727 {
1728 using type = std::remove_cvref_t<T>;
1729 // The cv-qualifiers of an array are those of its elements, which
1730 // std::remove_cvref_t removes; keep volatile for the element type.
1731 using element_type = std::remove_const_t<
1732 std::remove_extent_t<std::remove_reference_t<T>>>;
1733
1734 if constexpr (std::is_array_v<type> && character<element_type>
1735 && not std::is_volatile_v<element_type>)
1736 {
1738 t, std::extent_v<type>
1739 };
1740 }
1741 else if constexpr (character_pointer<type>)
1742 {
1744 t
1745 };
1746 }
1747 else if constexpr (std::is_array_v<type>)
1748 {
1749 // Other arrays (e.g. byte buffers) are compared by content.
1751 static_cast<const element_type*> (t), std::extent_v<type>
1752 };
1753 }
1754 else if constexpr (std::is_function_v<type>)
1755 {
1756 // Functions are compared by address, as function pointers.
1757 return &t;
1758 }
1759 else
1760 {
1761 return std::forward<T> (t);
1762 }
1763 }
1764
1765 // ------------------------------------------------------------------------
1766
1767 } // namespace detail
1768
1769 // --------------------------------------------------------------------------
1770
1771} // namespace micro_os_plus::micro_test_plus
1772
1773#if defined(__GNUC__)
1774#pragma GCC diagnostic pop
1775#endif // defined(__GNUC__)
1776
1777// ----------------------------------------------------------------------------
1778
1779#endif // defined(__cplusplus)
1780
1781// ----------------------------------------------------------------------------
1782
1783#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_INLINES_DETAIL_INLINES_H_
1784
1785// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.8 by Doxygen 1.18.0.