Search code examples
c++c++11assertvariadic-templatesvariadic-macros

How to write a c++ assert macro with a varying number of informational arguments?


I am trying to write a macro dbgassert similar to the standard assert. In addition to what assert does, I want to dbgassert print an arbitrary number of additional parameters (containing debugging information).

What I have so far is listed below, which is adapted from this SO answer. But I am having issue in my code with either variadic templates or macros. If I use at least one additional argument (the OK line), then dbgassert works as expected. But if I give no additional argument, then compilation fails (the problem line).

I have some experience with variadic template programming (like how to print a tuple), but I haven't used variadic macros before.

Can some please explain what's the proper way of writing this variadic parameters macro combination?

By the way, could someone explain the #EX magic in the macro? It shows the expression and works for me on gcc4.8.1. Is it universally supported?

Thanks,


Code:

//corrected reserved identifier issue and assumption issues per comments
#include <cassert>
#include <iostream>
using namespace std;

template <typename ...Args>
void realdbgassert(const char *msg, const char *file, int line, Args ... args) {
  cout << "Assertion failed! \nFile " << file << ", Line " << line << endl 
       << "  Expression: " << msg << endl;
  std::abort();
}

#define dbgassert(EX,...) \
  (void)((EX) || (realdbgassert (#EX, __FILE__, __LINE__, __VA_ARGS__),0))

int main() {
  dbgassert(1>2,"right","yes"); //OK
  dbgassert(1>2,"right"); //OK.
  //dbgassert(1>2); //Problem. compile error: expected primary-expression before ')' token
                  //#define dbgassert(EX,...) (void)((EX) || (realdbgassert (#EX, __FILE__, __LINE__, __VA_ARGS__)^,0))
}

Original version of the code.

#include <cassert>
#include <sstream>
using namespace std;

#ifdef __cplusplus
extern "C" {
#endif
extern void __assert (const char *msg, const char *file, int line);
#ifdef __cplusplus
};
#endif

template <typename ...Args>
void _realdbgassert(const char *msg, const char *file, int line, Args ... args) {
    stringstream os;
    //... do something
    __assert(msg,file,line);
}
#define dbgassert(EX,...) (void)((EX) || (_realdbgassert (#EX, __FILE__, __LINE__, __VA_ARGS__),0))

int main() {
  dbgassert(1==0,"right"); //Problem line: undefined reference to `__assert'
} 

Solution

  • Your problem is the value of __VA_ARGS__ which is empty in the problem case. So, when the preprocessor expands realdbgassert(#EX, __FILE__, __LINE__, __VA_ARGS__), the result is an unfinished parameter list realdbgassert("1>2", "foo.c", 42, ). Note that the parameter list is not correctly terminated due to the empty expansion of __VA_ARGS__.

    To fix this, you need to use some kind of trick. The best solution is, to tweak the circumstances so that __VA_ARGS__ includes the last unconditional argument, and pass that together with the optional ones at the end of the function call. This is best, because it's standard C.

    The other fix that I know of, is a gcc extension to the language: See this gcc documentation page for more detailed info, but you can fix your macro by adding a double ## in front of __VA_ARGS__:

    #define dbgassert(EX,...) \
      (void)((EX) || (realdbgassert (#EX, __FILE__, __LINE__, ## __VA_ARGS__),0))
    

    Ps:
    The # is the stringification operator of the preprocessor: it turns the value of the macro parameter into a string literal, i. e. instead of pasting 1>2 it pastes "1>2".