4

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:

  1. Simply remove the sizeof; I have very few cases in my code with otherwise unused variables and can just deal with them manually.
  2. Avoid using labdas within asserts.
  3. 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

Charles Ofria
  • 1,936
  • 12
  • 24
  • 1
    what is the point of `emp_assert` and how does it different from `assert` or `static_assert` ? – M.M Jun 18 '15 at 15:39
  • Two main differences: When I turn on a unit-tests flag, it reports errors without aborting (so I can test my asserts), and when I'm compiling with Emscripten (to asm.js) it uses Javascript alerts to provide information. – Charles Ofria Jun 18 '15 at 15:45
  • 1
    can you include some code that uses `emp_assert` and shows the problem? – M.M Jun 18 '15 at 16:07
  • Done! The new example should give you the error as-is. This error only occurs if I'm using a lambda in the assert; if you change the emp_assert it will work just fine. – Charles Ofria Jun 18 '15 at 16:37
  • What is the point of an assert with a variable, not existing in release builds (If you need a debug variable, put it into #ifndef NDEBUG ... #endif)? –  Jun 18 '15 at 16:46
  • and what you're actually looking for is just a way to make the emp_assert line do nothing in release mdoe? – M.M Jun 18 '15 at 16:46
  • @DieterLücking: In some cases, I have a command return a bool indicating success or failure. In such cases I will often save the return status and assert that it is true (since I need the command to run regardless, I can't put it in the assert directly). The variable I save the result in is then considered unused in release mode. This is easy to fix on a case-by-case basis, but I was wondering if there was a simple drop-in replacement for sizeof to accomplish this goal. – Charles Ofria Jun 18 '15 at 16:58
  • @MattMcNabb Exactly -- I want emp_assert to do nothing in release mode BUT I want any variables in it to still count as used. With a void-cast sizeof, the variables are used but no code is generated. – Charles Ofria Jun 18 '15 at 17:00
  • 1
    File a bug with the committee and ask them to remove the restriction on lambdas in unevaluated operands? – T.C. Jun 18 '15 at 17:00
  • 1
    @T.C. It looks like you're right; it's an actual restriction so that it's not possible to find and alternate solution that doesn't evaluate the lambda. On the other hand, there is a nice discussion of the issue here that might provide some possibilities through the use of constant expressions: https://stackoverflow.com/questions/22232164/why-are-lambda-expressions-not-allowed-in-an-unevaluated-operands-but-allowed-in – Charles Ofria Jun 18 '15 at 17:15
  • @CharlesOfria what do you mean by "used"? `sizeof` doesn't evaluate its operand. (What I was trying to get at is: are you just looking for it to do nothing and give no compiler warnings, or is there something else too, and if so, what?) – M.M Jun 18 '15 at 20:42
  • @MattMcNabb Indeed: do nothing and give no compiler warnings. – Charles Ofria Jun 18 '15 at 20:55

2 Answers2

3

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:

#define emp_assert(EXPR) {                             \
   constexpr bool __emp_assert_tmp = false && (EXPR);  \
   (void) __emp_assert_tmp;                            \
}

The constexpr ensures that the rest of the expression is evaluated at compile time. However the false && (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.

Charles Ofria
  • 1,936
  • 12
  • 24
0

Why using sizeof? I can't see any purpose of using sizeof in your example, why not just define like this:

#define emp_assert(exp) ((void) exp)

If you want to prevent unused variable warnings, you can using c++17 attribute [maybe_unused] to suppresses warnings on unused entities. For example:

[[maybe_unused]]
auto l = [](int i){return i < 8;};

Also, lambda-expression in unevaluated context is available on c++20 (but currently only gcc-9 implemented), so following code can compile with gcc-9 in -std=c++2a mode and your problem is automatic solved.

static_assert(sizeof([](int i){return i < 8;}) == 1);

See wandbox test for your example.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 1
    The sizeof() prevents any unexpected side-effects. If the assert has a function call that changes any variables, that could affect the results, but if it's in a sizeof() that will make sure that the call doesn't actually occur. – Charles Ofria Jul 15 '19 at 16:58
  • All of that said, you are correct that now C++17 solves one problem, and C++20 will solve the other. – Charles Ofria Jul 15 '19 at 16:58