-
-
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); | |
} |
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
ASSERT(a + b == 4, "addition");
expands into (by the definition at L5)
if(!(a + b == 4)) Assert("a + b == 4","addition" , __FILE__ , __LINE__).__ASSERT1__;
So you can see, if a + b == 4
is evaluated to false
, an Assert
instance gets constructed, which prints the error message like following
Assertion Failed: a + b == 4
Message : addition
File : main.cpp, line 33
.__ASSERT1__
is nothing but accessing a static member of Assert
. It does nothing and compiler will just eliminate it during optimization. Actually, the __ASSERT1__
member can be of any type. Defining Empty
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:
Assertion Failed: a + b == 4
Message : addition
File : main.cpp, line 33
a+1: 2
We decide to "invent" the syntax like this:
ASSERT(a + b == 4, "addition")(a+1);
which expands into (by the definition at L5)
if(!(a + b == 4)) Assert("a + b == 4","addition" , __FILE__ , __LINE__).__ASSERT1__(a);
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)
if(!(a + b == 4)) Assert("a + b == 4","addition" , __FILE__ , __LINE__).__REPORT__(a+1).__ASSERT2__;
which further expands into (by the definition at L2)
if(!(a + b == 4)) Assert("a + b == 4","addition" , __FILE__ , __LINE__).report("a+1", (a+1) ).__ASSERT2__;
Here .report("a+1", (a+1) )
will print the debug information we want, and then return the Assert
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,
ASSERT(a + b == 4, "addition")(a+1)(b);
expands into
if(!(a + b == 4)) Assert("a + b == 4","addition" , __FILE__ , __LINE__).report("a+1", (a+1) ).__ASSERT2__(b);
We got __ASSERT2__(b)
as a macro again. This line will expand into
if(!(a + b == 4)) Assert("a + b == 4","addition" , __FILE__ , __LINE__).report("a+1", (a+1) ).report("b", (b)).__ASSERT1__
and then it prints
Assertion Failed: a + b == 4
Message : addition
File : main.cpp, line 36
a+1: 2
b: 2
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,
#define __ASSERT1__(x) __REPORT__(x).__ASSERT1__
#define __REPORT__(x) report(#x,(x))
#define ASSERT(cond, msg) if(!(cond)) Assert(#cond,msg,__FILE__,__LINE__).__ASSERT1__
ASSERT(a + b == 4, "addition")(a)(b)
will expand into
if(!(a + b == 4)) Assert("a + b == 4","addition","test.cpp",36).report("a",(a)).__ASSERT1__(b);
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 no operator()(int)
defined in Assert::Empty
.
test.cpp:36:40: error: no match for call to ‘(const Assert::Empty) (int&)’
36 | ASSERT(a + b == 4, "addition")(a)(b);
| ^
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 members Assert.__ASSERT1__
and Assert.__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.
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.
Sorry, I'm new to Github, and I was looking at your code and can't understand it; and I can't find documentation on it; I only see Code, Revisions and Stars folders. Is there a documentation page? Those nested, recursive macros are dizzying, and I'm not sure what the intent is.