How best to prevent unused variable warnings in custom assert without sizeof?

1.1k views Asked by At

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

2

There are 2 answers

2
Charles Ofria On BEST ANSWER

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.

2
康桓瑋 On

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.