Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/libassert/assert-macros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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( \
Expand Down Expand Up @@ -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) { \
Expand Down
49 changes: 47 additions & 2 deletions include/libassert/expression-decomposition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@

LIBASSERT_BEGIN_NAMESPACE
namespace detail {
template<typename T>
inline constexpr bool is_pointer_or_member_pointer = std::is_pointer_v<strip<T>> || std::is_member_pointer_v<strip<T>>;

template<typename T,typename U>
inline constexpr bool is_NULL_comparable = (std::is_integral_v<strip<T>> && is_pointer_or_member_pointer<U>)
|| (std::is_integral_v<strip<U>> && is_pointer_or_member_pointer<T>);

// 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(?)
Expand Down Expand Up @@ -168,6 +175,7 @@ namespace detail {
struct expression_decomposer<nothing, nothing, nothing> {
explicit constexpr expression_decomposer() = default;


template<typename O> [[nodiscard]] constexpr auto operator<<(O&& operand) && {
return expression_decomposer<O, nothing, nothing>(std::forward<O>(operand));
}
Expand All @@ -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, >=) \
Expand Down Expand Up @@ -242,6 +248,40 @@ namespace detail {
// and rvalues as rvalues.
return std::forward<A>(a);
}

template<typename O>
constexpr auto operator==(O&& operand) &&
{
(void)operand;
if constexpr (is_NULL_comparable<A, O>)
{
if constexpr (std::is_integral_v<strip<A>>)
return expression_decomposer<std::nullptr_t,O, ops::eq>(nullptr, std::forward<O>(operand));
else
return expression_decomposer<A, std::nullptr_t, ops::eq>(
std::forward<A>(a), nullptr);
}
else {
return expression_decomposer<A, O, ops::eq>(std::forward<A>(a),
std::forward<O>(operand));
}
}

template <typename O> constexpr auto operator!=(O &&operand) && {
(void)operand;
if constexpr (is_NULL_comparable<A, O>) {
if constexpr (std::is_integral_v<strip<A>>)
return expression_decomposer<std::nullptr_t, O, ops::neq>(
nullptr, std::forward<O>(operand));
else
return expression_decomposer<A, std::nullptr_t, ops::neq>(
std::forward<A>(a), nullptr);
} else {
return expression_decomposer<A, O, ops::neq>(
std::forward<A>(a), std::forward<O>(operand));
}
}

#define LIBASSERT_GEN_OP_BOILERPLATE(functor, op) \
template<typename O> [[nodiscard]] constexpr auto operator op(O&& operand) && { \
return expression_decomposer<A, O, functor>(std::forward<A>(a), std::forward<O>(operand)); \
Expand Down Expand Up @@ -278,13 +318,18 @@ namespace detail {
// This use of std::forward may look surprising but it's correct
return std::forward<A>(a);
}

#define LIBASSERT_GEN_OP_BOILERPLATE(functor, op) \
template<typename O> [[nodiscard]] constexpr auto operator op(O&& operand) && { \
static_assert(!is_nothing<A>); \
using V = decltype(deduce_type(get_value())); /* deduce_type turns T&& into T while leaving T& as T& */ \
return expression_decomposer<V, O, functor>(get_value(), std::forward<O>(operand)); \
}
LIBASSERT_DO_GEN_OP_BOILERPLATE

LIBASSERT_GEN_OP_BOILERPLATE(ops::eq,==)
LIBASSERT_GEN_OP_BOILERPLATE(ops::neq,!=)

#undef LIBASSERT_GEN_OP_BOILERPLATE
};

Expand Down
54 changes: 53 additions & 1 deletion tests/unit/assertion_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>", "");
replace_all(message, ", std::less<std::string>", "");

return message;
}

Expand Down Expand Up @@ -224,6 +227,55 @@ TEST(LibassertBasic, PointerDiagnostics) {
);
}


TEST(LibassertBasic, NULLMacroComparison) {
int *p = nullptr;
CHECK(ASSERT(p != 0),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(p != 0);
| Where:
| p => int*: nullptr
| 0 => nullptr
)XX");

CHECK(ASSERT(0 != p),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(0 != p);
| Where:
| 0 => nullptr
| p => int*: nullptr
)XX");

CHECK(ASSERT(0 != p + 0),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(0 != p + 0);
| Where:
| 0 => nullptr
| p + 0 => int*: nullptr
)XX");

CHECK(ASSERT(p + 0 != 0),
R"XX(
|Assertion failed at <LOCATION>:
| 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;
Expand Down Expand Up @@ -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