Вопрос

I would like to create a custom version of the assert macro defined in <cassert>, that displays an error message when the assertion fails.


Desired usage:

custom_assert(AClass<T1, T2>::aBoolMethod(), "aBoolMethod must be true");


Flawed test implementations:

#define custom_assert(mCondition, mMessage) ...
// This fails because mCondition may have commas in it

#define custom_assert(..., mMessage)
// Not sure about this either - mMessage may be an expression containing commas
// as well

How can I correctly implement a custom assert that takes a boolean expression (with possible commas) as the first argument and a string expression (with possible commas) as the second argument?

Or is there a way to implement assertions without the use of macros?

Это было полезно?

Решение

You were quite close, what you need to use is simply this:

#define myAssert(message, ...) do { \
    if(!(__VA_ARGS__)) { \
        /*error code*/ \
    } \
} while(0)

The special preprocessor variable __VA_ARGS__ will expand to whatever was passed in the place of the three dots, including all comas.

Note that the preprocessor will not interprete commas in the condition in the very least, it will just paste them as is into the if() statement. Which is precisely what you want if you want to pass templated conditions, as hinted by the comments.

Commas in the message string are not a problem either, since the preprocessor understands about string literals and does not interprete anything within the double quotes.

Другие советы

The straightforward

assert(AClass<T1, T2>::aBoolMethod() && "aBoolMethod must be true");

fails:

error: macro "assert" passed 2 arguments, but takes just 1

but if you add an extra pair of parenthesis around the first argument, it works. Like this:

#include <cassert>

template <typename A, typename B>
struct C { 
  bool f() { return false; }
};

int main() {
  assert((C<int,int>().f()) && "some message, with comma");
  //     ^                ^
}

Note: it was also pointed out by Adam Rosenfield in a comment.

Perhaps the only benefit over the __VA_ARGS__ approach is that it doesn't dump yet another macro on the user. If you forget the parenthesis, you can get a compile time error, so I see it as a safe solution.

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.

I'm not entirely sure what you mean by a "boolean expression with commas." Simply wrapping macro expansions in commas (as seen in the samples below) protects against parse errors if you happen to use the default comma operator in your conditions, but the default comma operator does not do the same thing as &&. If you mean you want something like:

my_assert(condition1, condition2, message1, message2);

You're out of luck. How should any API tell where the conditions stop and the messages start. Just use &&. You can use the same tricks below for handling the message portion to also create a my_condition_set macro that allows you to write something like:

my_asssert(my_condition_set(condition1, condition2), message1, message2);

Getting to the message part, which is the tricky part and the most useful part that the standard assert macros tend to lack, the trick will come down to either using a custom message output type that overrides operator, (the comma operator) or to use a variadic template.

The macro uses variadic macro support and some tricks to deal with commas. Note that none of the code I'm posting here is tested directly; it's all from memory from custom assert macros I've written in the past.

The version using comma operator overloading, which works in C++98 compilers:

struct logger {
  template <typename T>
  logger& operator,(const T& value) {
    std::cerr << value;
    return *this;
  }
};

#define my_assert(condition, ...) do{ \
  if (!(condition)) { \
    (logger() , __VA_ARGS__); \
    std::terminate(); \
  } \
}while(false)

The logger() expression creates a new instance of the logger type. The list of arguments from __VA_ARGS__ is then pasted with commas separating each. These commas each invoke the comma operator left-to-right, which forwards the expression on to std::cerr. I usually use a custom log stream that handles writing to files, cerr, Windows' OutputDebugStringA, or whatever.

Using variadic templates in a C++11 compiler, this would be more like:

template <typename ...Ts>
void logger(Ts&&... argv) {
  std::cerr << your_string_format_function(argv...);
}

void logger(const char* fmt) {
  std::cerr << fmt;
}

void logger() {}

#define my_assert(condition, ...) do{ \
  if (!(condition)) { \
    logger(__VA_ARGS__); \
    std::terminate(); \
  } \
}while(false)

You'd need a format string function that actually works with variadic arguments or write an adapter for something like boost::format.

You could also use printf if you don't mind the lack of type-safety.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top