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.

M.M
  • 138,810
  • 21
  • 208
  • 365
Azeirah
  • 6,176
  • 6
  • 25
  • 44
  • 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. – Kninnug Sep 25 '15 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. – Konrad Rudolph Sep 25 '15 at 10:16
  • 1
    Don’t bother. It’s extremely complex. If you really, *really*, want to implement it, look at how [Catch](https://github.com/philsquared/Catch) does it. – Konrad Rudolph Sep 25 '15 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. – Kninnug Sep 25 '15 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. – Konrad Rudolph Sep 25 '15 at 10:32
  • If there were a simple "black magic" solution, we'd probably be writing `print("It's the {day} of {mon} and it's {temp} °C outside.")` instead of all these `<<` chains. – M Oehm Sep 25 '15 at 10:56
  • "filesFound is 0" Oh really!? Maybe also consider the usefulness of such prints... – Lundin Sep 25 '15 at 11:01
  • @Lundin Sometimes it is useful, sometimes it is not. In this example it is not very useful. – Azeirah Sep 25 '15 at 11:03
  • A failed assertion is not a warning! It's not even an error. It's a bug in your code, a use case that's not yet handled properly. Handling invalid input is expected, that's not what to use assertion for. – Kusalananda Jun 21 '16 at 21:27
  • Yes, your expectation of disappointment is entirely justified. – n. m. could be an AI Jul 10 '16 at 08:26

9 Answers9

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());
Mark
  • 166
  • 1
  • 3
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
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • 1
    I think he is asking of how the printout all the terms within any expression, your solution doesn't work in this case. – rkachach Sep 25 '15 at 10:34
  • 1
    @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...) – Serge Ballesta Sep 25 '15 at 10:52
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.

rkachach
  • 16,517
  • 6
  • 42
  • 66
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);)
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

Community
  • 1
  • 1
Thomas Sparber
  • 2,827
  • 2
  • 18
  • 34
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.
Lundin
  • 195,001
  • 40
  • 254
  • 396
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: main@prog.cc: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.

Kusalananda
  • 14,885
  • 3
  • 41
  • 52
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;
tony
  • 3,737
  • 1
  • 17
  • 18
  • 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. – Jonathan Leffler Jul 10 '16 at 03:43
  • 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. – tony Sep 03 '16 at 03:31
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.

Kingsley Chen
  • 276
  • 4
  • 12