Adding message to assert

2019-01-21 16:38发布

Hallo!

I'm looking for a way to add custom messages to assert statements. I found this questions Add custom messages in assert? but the message is static there. I want to do something like this:

assert((0 < x) && (x < 10), std::string("x was ") + myToString(x));

When the assertion fails I want the normal output plus for example "x was 100".

7条回答
Anthone
2楼-- · 2019-01-21 16:53

Yes, this is possible.

To enable expression like better_assert((0 < x) && (x < 10), std::string("x was ") + myToString(x));, we are supposed to have a corresponding macro in a form of

#define better_assert(EXPRESSION, ... ) ((EXPRESSION) ? \
(void)0 : print_assertion(std::cerr, \
"Assertion failure: ", #EXPRESSION, " in File: ", __FILE__, \ 
" in Line: ", __LINE__ __VA_OPT__(,) __VA_ARGS__))

in which print_assertion is a proxy function to do the assertion. When the EXPRESSION is evaluated false, all the debug information, the __VA_ARGS__, will be dumped to std::cerr. This function takes arbitrary numbers of arguments, thus we should implement a variadic templated function:

template< typename... Args >
void print_assertion(std::ostream& out, Args&&... args)
{
    out.precision( 20 );
    if constexpr( debug_mode )
    {
        (out << ... << args) << std::endl;
        abort();
    }
}

In the previous implementation, the expression (out << ... << args) << std::endl; make use of fold expression in C++17 (https://en.cppreference.com/w/cpp/language/fold); the constant expression debug_mode is related to the compilation options passed, which is can be defined as

#ifdef NDEBUG
    constexpr std::uint_least64_t debug_mode = 0;
#else
    constexpr std::uint_least64_t debug_mode = 1;
#endif

It also worth mentioning that the expression if constexpr( debug_mode ) makes use of constexpr if (https://en.cppreference.com/w/cpp/language/if) imported since C++17.

To wrap everything up, we have:

#ifdef NDEBUG
    constexpr std::uint_least64_t debug_mode = 0;
#else
    constexpr std::uint_least64_t debug_mode = 1;
#endif

template< typename... Args >
void print_assertion(std::ostream& out, Args&&... args)
{
    out.precision( 20 );
    if constexpr( debug_mode )
    {
        (out << ... << args) << std::endl;
        abort();
    }
}
#ifdef better_assert
#undef better_assert
#endif
#define better_assert(EXPRESSION, ... ) ((EXPRESSION) ? (void)0 : print_assertion(std::cerr, "Assertion failure: ",  #EXPRESSION, " in File: ", __FILE__, " in Line: ",  __LINE__ __VA_OPT__(,) __VA_ARGS__))

A typical test case demonstrating its usage can be:

double const a = 3.14159265358979;
double const b = 2.0 * std::asin( 1.0 );
better_assert( a==b, " a is supposed to be equal to b, but now a = ", a, " and b = ", b );

This will produce something error message like:

Assertion failure: a==b in File: test.cc in Line: 9 a is supposed to be equal to b, but now a = 3.1415926535897900074 and b = 3.141592653589793116
[1]    8414 abort (core dumped)  ./test

And the full source code is available in this repo: https://github.com/fengwang/better_assert

查看更多
迷人小祖宗
3楼-- · 2019-01-21 17:03

You are out of luck here. The best way is to define your own assert macro.

Basically, it can look like this:

#ifndef NDEBUG
#   define ASSERT(condition, message) \
    do { \
        if (! (condition)) { \
            std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \
                      << " line " << __LINE__ << ": " << message << std::endl; \
            std::terminate(); \
        } \
    } while (false)
#else
#   define ASSERT(condition, message) do { } while (false)
#endif

This will define the ASSERT macro only if the no-debug macro NDEBUG isn’t defined.

Then you’d use it like this:

ASSERT((0 < x) && (x < 10), "x was " << x);

Which is a bit simpler than your usage since you don’t need to stringify "x was " and x explicitly, this is done implicitly by the macro.

查看更多
叼着烟拽天下
4楼-- · 2019-01-21 17:04
#define ASSERT_WITH_MESSAGE(condition, message) do { \
if (!(condition)) { printf((message)); } \
assert ((condition)); } while(false)
查看更多
成全新的幸福
5楼-- · 2019-01-21 17:11

For the sake of completeness, I published a drop-in 2 files assert macro implementation in C++:

#include <pempek_assert.h>

int main()
{
  float min = 0.0f;
  float max = 1.0f;
  float v = 2.0f;
  PEMPEK_ASSERT(v > min && v < max,
                "invalid value: %f, must be between %f and %f", v, min, max);

  return 0;
}

Will prompt you with:

Assertion 'v > min && v < max' failed (DEBUG)
  in file e.cpp, line 8
  function: int main()
  with message: invalid value: 2.000000, must be between 0.000000 and 1.000000

Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:

Where

  • (I)gnore: ignore the current assertion
  • Ignore (F)orever: remember the file and line where the assertion fired and ignore it for the remaining execution of the program
  • Ignore (A)ll: ignore all remaining assertions (all files and lines)
  • (D)ebug: break into the debugger if attached, otherwise abort() (on Windows, the system will prompt the user to attach a debugger)
  • A(b)ort: call abort() immediately

You can find out more about it there:

Hope that helps.

查看更多
The star\"
6楼-- · 2019-01-21 17:13

going along with Konrad Rudolf's answer you can do it a bit more concise with

#include <assert.h>
#include <stdio.h>
#define ASSERT(condition,...) assert( \
    condition|| \
    (fprintf(stderr,__VA_ARGS__)&&fprintf(stderr," at %s:%d\n",__FILE__,__LINE__)) \
);

which also works in C,

it works using the general idea from some of the answers to the question you linked, but the macro allows it to be a little more flexible

查看更多
戒情不戒烟
7楼-- · 2019-01-21 17:14

A better alternative is to teach the debugger to stop on assert when it fails, then you could examine not only the x value but any other information including call stack. Perhaps, this is what you are really looking for. Sample implementation is mentioned here Ways to show your co-programmers that some methods are not yet implemented in a class when programming in C++

查看更多
登录 后发表回答