3

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'
} 
Community
  • 1
  • 1
thor
  • 21,418
  • 31
  • 87
  • 173
  • 2
    You're using [reserved identifiers](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier). Anyway, what's `__assert`? Certainly not a standard function. Or are you just trying to use an implementation detail that your implementation doesn't have? – chris Jul 25 '14 at 17:35
  • `.../ccZ47ZOL.o:test7.cpp:.text$_Z14_realdbgassertIIPKcEEvS1_S1_iDpT_[_Z14_realdbgassertIIPKcEEvS1_S1_iDpT_]+0x65): undefined reference to __assert'` – thor Jul 25 '14 at 17:38
  • 2
    @TingL Well, what did you expect? You haven't provided a definition for `__assert`. Or are you saying your standard library implementation provides such a function? – Praetorian Jul 25 '14 at 17:40
  • I thought `__assert` is the function behind the `assert` function. I saw it in SO here: http://stackoverflow.com/questions/9701229/c-assert-implementation-in-assert-h/9701408#9701408 – thor Jul 25 '14 at 17:48
  • I think the standard assert can't be a real function, and there must a real function behind the `assert` macro, right? Because `assert` reports problem lines, and if `assert` were a real function, it'd report the line number inside ``. Is there no standard handler (i.e. the real assert function) in the standards? – thor Jul 25 '14 at 17:50
  • @TingL, `assert` is the standard part. How an implementation implements it is up to them. – chris Jul 25 '14 at 17:54
  • And what made you assume the *real function* that the `assert` macro expands to would be named `__assert`? The standard doesn't anything about what that macro expands to. It may not even expand into a single function call, could be several expressions for all you know. You need to write your own `assert` like implementation. – Praetorian Jul 25 '14 at 17:58
  • Well, as I said, I saw it on that SO answer, and was too quick to make assumptions. But that's what made me assume the real function is `__assert`. – thor Jul 25 '14 at 18:00
  • http://stackoverflow.com/questions/21554355/what-does-the-operator-do-in-macros?lq=1 - but please confine yourself to asking one question per question. – ecatmur Jul 25 '14 at 19:02

5 Answers5

5

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".

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • I was struggling to write an answer on my handy only to find you beat me to it. Good explanation. You got my upvote. – ds27680 Jul 25 '14 at 21:27
1

Place the token pasting operator (##) before __VA_ARGS__. This will have as effect deleting of the comma before __VA_ARGS__ if __VA_ARGS__ is empty.

Your macro will be then:

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

Please note that the token pasting is an GNU CPP extension as one of the other posters correctly mentioned (see https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html).

The MS compiler (tested under VS2010) does not need the token pasting, it simply removes the trailing comma if __VA_ARGS__ is empty see: http://msdn.microsoft.com/en-us/library/ms177415(v=vs.110).aspx

ds27680
  • 1,993
  • 10
  • 11
1

Just as a note, I was able to figure out another means to solve the trailing comma issue, in addition to @cmaster and @ds27680's solution. Since having __VA_ARGS__ leads to an extra comma, I can pack __VA_ARGS__ into a std::tuple or a function call, and use the tuple/result as a parameter of the real function. Now empty __VA_ARGS__'s won't be a problem because its packed into one valid value (i.e. either an empty tuple or the return value of a nullary function). I guess this is longer in code, but a bit more portable without involving ##.

The above two cases are shown in the dbgassert and dbgassert1 macros in the code below, respectively.

#include <cassert>
#include <iostream>
#include <tuple>
using namespace std;

template <typename ...Args>
string print_tuple(tuple<Args...> tp) {
  return ""; //print the tuple...
}

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

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

void realdbgassert1(string info,const char *msg, const char *file, int line) {
  cout << "Assertion failed! \nFile " << file << ", Line " << line << endl 
       << "  Expression: " << msg << endl
       << "  Info: " << info << endl;
  std::abort();
}

template <typename ...Args>
string print_info(Args ... args) {
  return "";  //print stuff
}

#define dbgassert1(EX,...) \
  (void)((EX) || (realdbgassert1 (print_info(__VA_ARGS__),#EX,__FILE__, __LINE__),0))


int main() {
  dbgassert(1>2,"right","yes"); //OK
  dbgassert(1>2,"right"); //OK
  dbgassert(1>2); //OK now
  dbgassert1(1>2); //OK too
}
thor
  • 21,418
  • 31
  • 87
  • 173
  • I was already thinking about wrapping the unconditional parameters into an object so that they can be passed implicitly to a variadic member function like this `WrapperConstructor(#EX, __FILE__, __LINE__).assert(__VA_ARGS__)`. But your approach is probably more C++ style. +1 for the first answer to actually provide a full, portable solution :-) – cmaster - reinstate monica Jul 26 '14 at 07:17
0

You're assuming that assert is implemented by calling __assert. This may well be how one particular implementation works, but certainly cannot be depended on in general.

Instead, follow the documentation: test your condition and on failure emit diagnostic information to standard error, then call std::abort.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Thanks. But I have additional issues with the variadic macros. Please see the update. – thor Jul 25 '14 at 18:57
  • @TingL See http://stackoverflow.com/questions/5588855/standard-alternative-to-gccs-va-args-trick – ecatmur Jul 25 '14 at 19:00
-1

You have to write the contents of the function __assert - if you specified it's extern you should attach the file that contains the function's definition to the compilation process. If you don't know how to write a multiple-file program, I can't really help you.

Arusekk
  • 827
  • 4
  • 22