Based on advice in http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ I've been working with my own version of assert (called emp_assert
) for a while now. As such, when NDEBUG is set, my assert looks like:
#define emp_assert(EXPR) ((void) sizeof(EXPR) )
This definition assures that any variables in EXPR still count as "used" in the compiler, but do not affect run-time performance.
Unfortunately, I've recently discovered that any use of lambdas within an assert produce a compilation error since lambdas cannot be put into a sizeof.
My options seem to be:
- Simply remove the sizeof; I have very few cases in my code with otherwise unused variables and can just deal with them manually.
- Avoid using labdas within asserts.
- Come up with a replacement for sizeof that would have the same effect.
Option 1 is currently my top choice, but I use this assert system with many projects and would likely stumble on problems for some time to come.
Option 2 seems too limiting, especially since I can imagine unexpected interactions in the future.
Option 3 would be the most elegant, drop-in solution, but I could use help coming up with an idea for how to accomplish it.
EDIT: Here is some example code to illustrate the problem.
#include <iostream>
#include <algorithm>
#include <vector>
#define NDEBUG
// Relevant excerpt from "emp_assert.h"
#ifdef NDEBUG
#define emp_assert(EXPR) ((void) sizeof(EXPR))
#else
#define emp_assert(EXPR) \
do { if ( !(EXPR) ) { \
std::cerr << "Assert Error (In " << __FILE__ \
<< " line " << __LINE__ \
<< "): " << #EXPR << std::endl; \
abort(); } \
} while (0)
#endif
// Code to trigger the problem (asserting that all ints in a vector are less than 8.)
int main()
{
std::vector<int> v = {1, 2, 3, 4, 8};
emp_assert( std::all_of(v.begin(), v.end(), [](int i){return i < 8;}) );
}
Comment out the #define NDEBUG
to see how the code will compile correctly and trip the assert when run. (Remove the 8 from the vector if you don't want the assert to trip).
I compiled using g++ -std=c++11 file.cc
I believe I figured out a solution. Since lambda expressions not allowed in unevaluated operands but they ARE allowed in the unevaluated portions of constant expressions, we should be able to exploit that fact.
Specifically, I've set my #define when NDEBUG is ON to:
The
constexpr
ensures that the rest of the expression is evaluated at compile time. However thefalse && (EXPR)
short-circuits so that EXPR is never evaluate but variables in it are not considered unused.The variable
__emp_assert_tmp
is created inside its own scope, so multiple asserts do not conflict. And the highly specific name will make sure that we're not accidentally shadowing a variable used in EXPR. Any reasonable optimizing compiler should remove the extra variable entirely, so I don't believe it should cause an optimization issue.In my tests, nothing in EXPR is executed, lambda don't trip it up, and everything seems to work correctly.
Let me know if you see any problems I'm missing.