13

Some time ago, I made this beautiful assert macro for c and c++ programs

#define ASSERT(truthy, message) \
     if (!(truthy)) \
     {\
         cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy << endl;\
     }

Scatter ASSERT calls throughout your code, and it will warn you whenever the truthy value is not truthy! Very handy during development to remind you of potential mistakes.

ex

ASSERT(filesFound > 0, "Couldn't find any files, check your path!");

When filesFound is 0, the macro will print out

Couldn't find any files, check your path! on line 27 in file openFiles.c. Check was filesFound > 0

Now what I want it to print, to give me even more relevant information, is the value of any variables passed into the truthy parameter. Like this

Couldn't find any files, check your path! on line 27 in file openFiles.c. Check was filesFound > 0, filesFound is 0

This seems lisp-like territory, I wonder, is there any black magic c preprocessing that I can use to evaluate variables and functions to their values, without evaluating the truthy statement?

I assume to be disappointed.

10
  • Standard procedure is to wrap the macro in a do{ ... }while(0); statement, then you can use variables local to the do-while. Note that the standard assert often prints relevant information as well. A common hack is to do assert(truthy && "The foo was barred!"); to print the message with the expression. Commented Sep 25, 2015 at 10:04
  • 1
    @Kninnug The real reason to use do {…} while (false) is so that the user of the macro is forced/enabled to put a semicolon after the macro invocation. The local variables would also work in OP’s macro. Commented Sep 25, 2015 at 10:16
  • 1
    Don’t bother. It’s extremely complex. If you really, really, want to implement it, look at how Catch does it. Commented Sep 25, 2015 at 10:17
  • @KonradRudolph what I meant was in the form of do{ int val = ... }while(0); such that val is not visible outside the macro and doesn't interfere with other local variables. Commented Sep 25, 2015 at 10:31
  • @Kninnug I know. But you don’t need do…while for that here, the same works in OP’s code inside the scope of the if body. Commented Sep 25, 2015 at 10:32

9 Answers 9

1

An alternative solution which I've always used is to support varargs in the macro and then force the assert user to specify the relevant message / variables - it's a little bit of extra work each time, but on the plus side you can get exactly the formatting that you want and include information not available in the "truthy" bit, e.g:

#define ASSERT(truthy, message, ...) \
if (!(truthy)) \
{\
    MyAssertHandler(__LINE__, __FILE__, #truthy, message, ##__VA_ARGS__);
}

Then you're handler is just a fairly standard var-arg function that can use e.g. vsnprintf to generate the message and output it, e.g. off the top of my head:

void MyAssertHandler(int line, const char* file, const char* expressionStr, const char* format, ...)
{
    // Note: You probably want to use vsnprintf instead to first generate
    //       the message and then add extra info (line, filename, etc.) to
    //       the actual output 
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);

    // Log to bug database, DebugBreak() if a debugger is attached, etc.
}

usage:

ASSERT(IsBlah(), "BlahBlah: x = %.2f, name = %s", GetX(), GetName());
Sign up to request clarification or add additional context in comments.

Comments

0

I cannot imagine a way to do it... except by passing another parameter

#define ASSERT_PARAM(truthy, message, param) \
     if (!(truthy)) \
     {\
         cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy  << ", value was " << param << endl;\
     }

You would use it that way:

ASSERT_PARAM(filesFound > 0, "Couldn't find any files, check your path!", filesFound);

getting:

Couldn't find any files, check your path! on line 27 in file openFiles.c. Check was filesFound > 0, value was 0

2 Comments

I think he is asking of how the printout all the terms within any expression, your solution doesn't work in this case.
@redobot: You are right. But it is a quick and robust workaround. With ASSERT_PARAM, ASSERT_PARAM2, ASSERT_PARAM3 and ASSERT_PARAM4, you should cover 99% of use cases. I always want test and diagnostic code to be as simple as possible, unless it comes from a strong reliable source (and I think I am not...)
0

What you are trying to do sounds very complicated. I'm afraid in C++ it's not possible.

Technically what you are evaluating is a bool expression so you can pass it to a parser whenever the assertion fails. The parser then will build the expression tree, get the leaves (elements of the expression) and return them. The returned values then should be printed out. To do that you will need support for reflection which is actually not supported in C++ AFAIK.

3 Comments

It is possible — the Catch library does it. But it’s hellishly difficult.
AFAIK standard C++ doesn't. You can take a look to the reasons behind the decision: stackoverflow.com/questions/359237/…
Read my comment again: the Catch library does it. In standard C++.
0

Maybe not the dream solution, but you can pass whole statements to a macro.

#define ASSERT(trusty, action) if (!trusty) { action }

ASSERT(trusty, cout << a << b;)
ASSERT(trusty, printf("%d, %f\n", a, b);)

Comments

0

I think you can split up the truthy Expression like they do it in the first answer here and then you can probably print the individual values. But I'm not sure if it actually works.

The printing could then be resulved using a variadic template function

Comments

0

Perhaps you could compromise and only allow 2 variables and 1 operator in the assertion expression? If so, you could make an ad hoc solution like this:

#include <iostream>
#include <string>

#define STRINGIFY(x) #x

#define BIN_ASSERT(obj1, op, obj2, msg)                                 \
  if(!(obj1 op obj2))                                                   \
  {                                                                     \
    std::cout << msg << " on line " << __LINE__                         \
         << " in file " << __FILE__                                     \
         << "." << std::endl                                            \
         << "Check was "                                                \
         << STRINGIFY(obj1) STRINGIFY(op) STRINGIFY(obj2)               \
         << "." << std::endl                                            \
         << "Operator " << #obj1 << ": " << obj1                        \
         << "." << std::endl                                            \
         << "Operator " << #obj2 << ": " << obj2                        \
         << "." << std::endl;                                           \
  }


int main (void)
{
  int x = 2;
  int y = 3;
  std::string s1 = "hello";
  std::string s2 = "world";

  BIN_ASSERT(1, +, -1, "Value zero"); std::cout << std::endl;
  BIN_ASSERT(x, ==, y, "Numbers not equal"); std::cout << std::endl;
  BIN_ASSERT(s1, ==, s2, "Strings not equal"); std::cout << std::endl;
}

Output:

Value zero on line 30 in file test.c.
Check was 1+-1.
Operator 1: 1.
Operator -1: -1.

Numbers not equal on line 31 in file test.c.
Check was x==y.
Operator x: 2.
Operator y: 3.

Strings not equal on line 32 in file test.c.
Check was s1==s2.
Operator s1: hello.
Operator s2: world.

Comments

0

I wonder if having the macro take a message is really that useful. A failed assertion is a message to the developer that there is a bug in the code that caused an exceptional behaviour or put the program in an unacceptable state. The user has less to do with it (if they even have access to the source code).

The code below defines an ASSERT macro that takes a boolean expression, evaluates it and prints an informational message. The message contains a value that you've asked to inspect upon failing the assertion.

The macro, just like the standard assert() macro (in <cassert>) goes on to call abort() (from <cstdlib>) to cause an abnormal program termination. This is what you want, because the program entered a state in which it didn't know what more to do.

I'm using std::printf() here for brevity. You do whatever you want.

#include <cstdlib>
#include <cstdio>

#define ASSERT(value, inspect)                                                 \
    if (!(value)) {                                                            \
        std::printf("ASSERTION FAILED: '%s', %s is %d: %s@%s:%d\n", #value,    \
                    #inspect, inspect, __func__, __FILE__, __LINE__);          \
        abort();                                                               \
    }

int foo() { return 42; }

int main()
{
    // ...
    ASSERT(foo() - 40 == 1, foo());
    //...
}

Program run:

$ ./a.out
ASSERTION FAILED: 'foo() - 40 == 1', foo() is 42: [email protected]:16
Abort

It's not possible to do exactly what you ask for without adding more parameters to the macro. At some point you'll have to stop and realize that you're spending time on creating a text string that you do not want to see.

Comments

0

You need to build an expression 'grabber' / builder.

The macro would become something like:

#define ASSERT_PARAM(truthy, message, param) \
 if (!(truthy)) \
 {\
     Grabber g;
     g << #truthy; // grab expression as string
     g % truthy;  // grab expression and values
     cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy  << ", value was " << param << endl;\
     cout << g; \
 }

What does Grabber do?

It is a bunch of crazy C++ that builds up an expression. It would overload every operator to 'grab' the params of the operator. Every operator returns a reference to the grabber, so it can grab the next operator. ie

Grabber g;
g % filesFound > 0;

Since % (and * and /) have high precedence, the above parses like:

((g % filesFound) > 0)

If template<typename T> Grabber::operator%(T const & val) just records (or prints) the value passed in (ie filesFound), and - importantly - returns itself (g) so that it becomes part of the next expression: ie it becomes g > 0. Causing template<typename T> Grabber::operator>(T const & val) to be called, and > 0 to be recorded.

Then cout << g can spew out everything grabbed.

As mentioned above "It is possible — the Catch library does it. But it’s hellishly difficult".

P.S. you should wrap your macro in a do ... while 0 like this:

#define ASSERT_PARAM(truthy, message, param) \
 do \
 { \
   if (!(truthy)) \
   {\
     cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy  << ", value was " << param << endl;\
     cout << g; \
   } \
 } while (0)

What you have currently means that this is valid code:

ASSERT(foo != 0)
else
{
}

And this is NOT valid code:

if (foo != nullptr)
   ASSERT(foo->bar != nullptr);
else
   x = 10;

2 Comments

How does the g % truthy; line work? Shouldn't the truthy be parenthesized so that if truthy is filesFound > 0 you get g % (filesFound > 0) rather than (g % filesFound) > 0? I'm assuming that the Grabber overloads the % operator.
In most macros, yes, you want to parenthesize but not here - that would make it NOT work, strangely enough. g % (filesFound > 0) means that filesFound > 0 is evaluated first - but you don't want that - you want g to grab each piece of the expression, not just the result of the expression.
0

Surprisingly, I solved a similar problem before, but I'm not sure if it could help you in this case.

The original solution was proposed by Andrei Alexandrescu in the article Enhancing Assertions, and with no question, relying on some macro tricks.

This amazing facility can be used as the following:

string s1, s2;
...
SMART_ASSERT(s1.empty() && s2.empty())(s1)(s2);

And if something goes wrong, the message would be displayed

Assertion failed in matrix.cpp: 879412:
Expression: 's1.empty() && s2.empty()'
Values: s1 = "Wake up, Neo"
        s2 = "It's time to reload."

Be noted that, the SMART_ASSERT can capture infinite variables, theoretically.

For implementation details, please check out the article.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.