-
-
Save iamalbert/de463e388173ca6f862c2a730fb64cee to your computer and use it in GitHub Desktop.
#define __ASSERT1__(x) __REPORT__(x).__ASSERT2__ | |
#define __ASSERT2__(x) __REPORT__(x).__ASSERT1__ | |
#define __REPORT__(x) report(#x,(x)) | |
#define ASSERT(cond, msg) if(!(cond)) Assert(#cond,msg,__FILE__,__LINE__).__ASSERT1__ | |
#include <iostream> | |
struct Assert { | |
template<class T> | |
Assert(const char * cond, T && msg, const char * file, int line) { | |
std::cerr << "Assertion Failed: " << cond << "\n"; | |
std::cerr << "Message : " << msg << "\n"; | |
std::cerr << "File : " << file << ", line " << line << "\n"; | |
} | |
template<class T> | |
Assert & report(const char * name, T&& value){ | |
std::cerr << " " << name << ": " << value << "\n"; | |
return *this; | |
} | |
struct Empty {}; | |
static constexpr Empty __ASSERT1__ {}, __ASSERT2__ {}; | |
~Assert(){ exit(-1); } | |
}; | |
#include "Assert.hpp" | |
int main(){ | |
int a = 1, b = 2; | |
std::cout << sizeof(Assert) << "\n"; | |
ASSERT(a + b == 4, "addition")(a)(b); | |
} |
That's brilliant! I found this page by searching for smart_assert, since a very old backup of a suchly named boost folder that I have was not in the current boost, or at least I could not find it; but now I had a second look at that and it seems to be a totally different smart_assert by somebody else. I could not understand that other code either, but due to the sheer size of it, rather; I much prefer yours; and I think I'm going to use it. One feature I wish it had is a way to stop execution, like debug break, rather than abort.
A rather crazy thing I've been thinking about is having targets ALPHA and BETA, between DEBUG and RELEASE, that change the behavior of asserts to logging assertion failures and continue running (with or without "fixing" things). For example, if an addition should never overflow, but saturating the result would be a preferable compromise to shutting down the program, in DEBUG mode, the assert would stop execution; in ALPHA it would log the error to a file and ask the user whether to continue; in BETA it would log silently and continue; and in RELEASE mode it would just saturate the result and continue.
I am surprised someone notices this 5-year old post. :)
This is a hacky way to do infinite expansion of macros by abusing the preprocessor. If you're new to C++, you don't need to take this trick too seriously.
Better assertion message
expands into (by the definition at L5)
So you can see, if
a + b == 4
is evaluated tofalse
, anAssert
instance gets constructed, which prints the error message like following.__ASSERT1__
is nothing but accessing a static member ofAssert
. It does nothing and compiler will just eliminate it during optimization. Actually, the__ASSERT1__
member can be of any type. DefiningEmpty
struct is just my personal preference.Print debug info
Sometimes if assertion fails, we need to print some expression for debugging purpose. Here we wish it to print the value of
a+1
in case assertion fails:We decide to "invent" the syntax like this:
which expands into (by the definition at L5)
Here
__ASSERT1__(a)
is no longer accessing a member, but a macro. This is why we need to force macro and static member to have the same names, and probably why it looks so confusing.The macro further expands into (by the definition at L1)
which further expands into (by the definition at L2)
Here
.report("a+1", (a+1) )
will print the debug information we want, and then return theAssert
instance itself. Therefore,.__ASSERT2__
is simply accessing its static member and will be treated as a no-op by compiler again.More expressions
It's called "smart assert" because you can append infinite number of expressions after
ASSERT(...)
as long as you wrap them in parenthesis.For example,
expands into
We got
__ASSERT2__(b)
as a macro again. This line will expand intoand then it prints
Why mutual recursion?
You might want to ask, why do we have to use mutual recursion? It seems a simple recursion could do the expansion. However,
will expand into
and then the expansion stops here.
This is a indirect Self-Referential Macro and
__ASSERT1__(b)
will not continue to expand. It finally leads to a compile error as there is nooperator()(int)
defined inAssert::Empty
.So we have no choice but to use mutual recursion to work around.
In conclusion, by mutual recursion of between macros
__ASSERT1__
and__ASSERT2__
, and by two useless membersAssert.__ASSERT1__
andAssert.__ASSERT2__
, we manage to coin a confusing syntax and call it a "smart" assert, which is usually builtin function in any sane programming language. C macros doesn't make any sense. It's a legacy tool and doesn't worth beginners' time.