From e3d52f5a62ffbcb48d8ca8cae7c425d4aded1315 Mon Sep 17 00:00:00 2001 From: ZXShady <153229951+ZXShady@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:35:02 +0200 Subject: [PATCH] Allow NULL comparison Allow NULL macro comparison --- include/libassert/assert-macros.hpp | 2 + .../libassert/expression-decomposition.hpp | 49 ++++++++++++++++- tests/unit/assertion_tests.cpp | 54 ++++++++++++++++++- 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/include/libassert/assert-macros.hpp b/include/libassert/assert-macros.hpp index e7c85c6..075d150 100644 --- a/include/libassert/assert-macros.hpp +++ b/include/libassert/assert-macros.hpp @@ -205,6 +205,7 @@ LIBASSERT_END_NAMESPACE #define LIBASSERT_INVOKE(expr, name, type, failaction, ...) \ do { \ + if constexpr(false) {(void)(expr);}\ LIBASSERT_WARNING_PRAGMA_PUSH \ LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA \ auto libassert_decomposer = libassert::detail::expression_decomposer( \ @@ -263,6 +264,7 @@ LIBASSERT_END_NAMESPACE auto libassert_decomposer = libassert::detail::expression_decomposer( \ libassert::detail::expression_decomposer{} << expr \ ); \ + if constexpr (false){(void)(expr);}\ decltype(auto) libassert_value = libassert_decomposer.get_value(); \ constexpr bool libassert_ret_lhs = libassert_decomposer.ret_lhs(); \ if constexpr(check_expression) { \ diff --git a/include/libassert/expression-decomposition.hpp b/include/libassert/expression-decomposition.hpp index 5183b93..807547b 100644 --- a/include/libassert/expression-decomposition.hpp +++ b/include/libassert/expression-decomposition.hpp @@ -14,6 +14,13 @@ LIBASSERT_BEGIN_NAMESPACE namespace detail { + template + inline constexpr bool is_pointer_or_member_pointer = std::is_pointer_v> || std::is_member_pointer_v>; + + template + inline constexpr bool is_NULL_comparable = (std::is_integral_v> && is_pointer_or_member_pointer) + || (std::is_integral_v> && is_pointer_or_member_pointer); + // Lots of boilerplate // std:: implementations don't allow two separate types for lhs/rhs // Note: is this macro potentially bad when it comes to debugging(?) @@ -168,6 +175,7 @@ namespace detail { struct expression_decomposer { explicit constexpr expression_decomposer() = default; + template [[nodiscard]] constexpr auto operator<<(O&& operand) && { return expression_decomposer(std::forward(operand)); } @@ -191,8 +199,6 @@ namespace detail { LIBASSERT_GEN_OP_BOILERPLATE(ops::shl, <<) \ LIBASSERT_GEN_OP_BOILERPLATE(ops::shr, >>) \ LIBASSERT_GEN_OP_BOILERPLATE_SPACESHIP \ - LIBASSERT_GEN_OP_BOILERPLATE(ops::eq, ==) \ - LIBASSERT_GEN_OP_BOILERPLATE(ops::neq, !=) \ LIBASSERT_GEN_OP_BOILERPLATE(ops::gt, >) \ LIBASSERT_GEN_OP_BOILERPLATE(ops::lt, <) \ LIBASSERT_GEN_OP_BOILERPLATE(ops::gteq, >=) \ @@ -242,6 +248,40 @@ namespace detail { // and rvalues as rvalues. return std::forward(a); } + + template + constexpr auto operator==(O&& operand) && + { + (void)operand; + if constexpr (is_NULL_comparable) + { + if constexpr (std::is_integral_v>) + return expression_decomposer(nullptr, std::forward(operand)); + else + return expression_decomposer( + std::forward(a), nullptr); + } + else { + return expression_decomposer(std::forward(a), + std::forward(operand)); + } + } + + template constexpr auto operator!=(O &&operand) && { + (void)operand; + if constexpr (is_NULL_comparable) { + if constexpr (std::is_integral_v>) + return expression_decomposer( + nullptr, std::forward(operand)); + else + return expression_decomposer( + std::forward(a), nullptr); + } else { + return expression_decomposer( + std::forward(a), std::forward(operand)); + } + } + #define LIBASSERT_GEN_OP_BOILERPLATE(functor, op) \ template [[nodiscard]] constexpr auto operator op(O&& operand) && { \ return expression_decomposer(std::forward(a), std::forward(operand)); \ @@ -278,6 +318,7 @@ namespace detail { // This use of std::forward may look surprising but it's correct return std::forward(a); } + #define LIBASSERT_GEN_OP_BOILERPLATE(functor, op) \ template [[nodiscard]] constexpr auto operator op(O&& operand) && { \ static_assert(!is_nothing); \ @@ -285,6 +326,10 @@ namespace detail { return expression_decomposer(get_value(), std::forward(operand)); \ } LIBASSERT_DO_GEN_OP_BOILERPLATE + + LIBASSERT_GEN_OP_BOILERPLATE(ops::eq,==) + LIBASSERT_GEN_OP_BOILERPLATE(ops::neq,!=) + #undef LIBASSERT_GEN_OP_BOILERPLATE }; diff --git a/tests/unit/assertion_tests.cpp b/tests/unit/assertion_tests.cpp index ce1d534..4539ff5 100644 --- a/tests/unit/assertion_tests.cpp +++ b/tests/unit/assertion_tests.cpp @@ -101,11 +101,14 @@ std::string normalize(std::string message) { // clang does T * replace_all(message, "void *", "void*"); replace_all(message, "char *", "char*"); + replace_all(message, "int *", "int*"); + // clang does T[N], gcc does T [N] replace_all(message, "int [5]", "int[5]"); // msvc includes the std::less replace_all(message, ", std::less", ""); replace_all(message, ", std::less", ""); + return message; } @@ -224,6 +227,55 @@ TEST(LibassertBasic, PointerDiagnostics) { ); } + +TEST(LibassertBasic, NULLMacroComparison) { + int *p = nullptr; + CHECK(ASSERT(p != 0), + R"XX( + |Assertion failed at : + | ASSERT(p != 0); + | Where: + | p => int*: nullptr + | 0 => nullptr + )XX"); + + CHECK(ASSERT(0 != p), + R"XX( + |Assertion failed at : + | ASSERT(0 != p); + | Where: + | 0 => nullptr + | p => int*: nullptr + )XX"); + + CHECK(ASSERT(0 != p + 0), + R"XX( + |Assertion failed at : + | ASSERT(0 != p + 0); + | Where: + | 0 => nullptr + | p + 0 => int*: nullptr + )XX"); + + CHECK(ASSERT(p + 0 != 0), + R"XX( + |Assertion failed at : + | ASSERT(p + 0 != 0); + | Where: + | p + 0 => int*: nullptr + | 0 => nullptr + )XX"); + + if constexpr(false) + { + ASSERT(0 == p); + ASSERT(p == 0); + ASSERT(0 == p + 0); + ASSERT(p + 0 == 0); + } +} + + TEST(LibassertBasic, LiteralFormatting) { const uint16_t flags = 0b000101010; const uint16_t mask = 0b110010101; @@ -943,4 +995,4 @@ TEST(LibassertBasic, DebugAssert) { // recursion / recursion folding // Complex type resolution // non-conformant msvc preprocessor -// source location unit test +// source location unit test \ No newline at end of file