Question

Currently I am writing unit tests in C++ with CppUnit. Recently I needed to check that an exception is thrown in a specific case using CppUnits macro:

CPPUNIT_ASSERT_THROW(
    boost::get<FooClassInBoostVariant>(m_boostVariantFooOrBar),
    boost::bad_get);

the warning during the compilation of the test surprised me (on VS2010, but will be a warning on other compilers as well...):

warning C4127: conditional expression is constant

I looked into the macro definition of CppUnit and found the following:

do {                                                            \
  bool cpputExceptionThrown_ = false;                           \
  try {                                                         \
     expression;                                                \
  } catch ( const ExceptionType & ) {                           \
     cpputExceptionThrown_ = true;                              \
  }                                                             \
                                                                \
  if ( cpputExceptionThrown_ )                                  \
     break;                                                     \
                                                                \
  CPPUNIT_NS::Asserter::fail(                                   \
                 "Expected exception: " #ExceptionType          \
                 " not thrown.",                                \
                 CPPUNIT_SOURCELINE() );                        \
} while ( false )

Well, I totally understand how this works, the do while loop is executed only once, because of the false, and the break is used to not execute the Asserter::fail() part. But why are they doing it like this? It - of course - triggers the compiler warning, as the break condition for the while loop is obviously always "false". But isn't there a more elegant way to do this? I usually adhere to the no-warning-compilation principle, so this really bugs me.

So my question really is, why didn't they implement it like this:

{                                                               \
  bool cpputExceptionThrown_ = false;                           \
  try {                                                         \
    expression;                                                 \
  } catch ( const ExceptionType & ) {                           \
    cpputExceptionThrown_ = true;                               \
  }                                                             \
                                                                \
  if ( !cpputExceptionThrown_ ) {                               \
    CPPUNIT_NS::Asserter::fail(                                 \
                 "Expected exception: " #ExceptionType          \
                 " not thrown.",                                \
                 CPPUNIT_SOURCELINE() );                        \
  }                                                             \
}

Thanks in advance!

-Hannes

Was it helpful?

Solution 2

OK, I guess I found the answer by myself:

http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ gives the explanation.

It is actually a common practice to wrap multi-line macros in do { } while (false);. This is a workaround to allow for using those macros, e.g., in un-braced if else.

if (condition_a)
    MULTI_LINE_MACRO();
else
    MULTI_LINE_MACRO_2();

The result would be that unexpectedly only the first line gets executed, which definitely leads to unexpected behaviour. So they were not entirely incompetent I guess...

http://kernelnewbies.org/FAQ/DoWhile0 also explains why my solution would not work. The MULTI_LINE_MACRO(); within the if would expand, e.g., to if (condition_a) { /* macro stuff */ } ; else // << never executed because of the ; above.

So I guess I have to disable the warning. GCC has a workaroud for this ( ({ MACRO }) ), called a Statement Expression, but I don't think this works on VS2010.

OTHER TIPS

The reason is to make the assertion one statement. Consider these two uses of the macro:

CPPUNIT_ASSERT_THROW(foo(), MyException);  // a
CPPUNIT_ASSERT_THROW(foo(), MyException)   // b - without trailing `;`!
doSomething();

With their code, you'd get an error with //b, since the code expands to do { ... } while (false) doSomething(); - you'd be missing the ; after the condition.

With your code, //b would happily compile, but //a could give you an "empty statement" warning, since that line would expand to { ... };, with the superfluos ; after the block.

Why they force you to use //a I don't know - but I like //b way more because it's just consistent to have a ; after each line. One does not have to distinguish lines with assertions from normal statements.

PS: I am not sure but there might be more differences between { ... } blocks and do {...} while(false) statements that will allow to put an assertion macro in places where simple blocks are not allowed.

Edit: with C++11, you could use a lambda (define and call it in one place):

#define CPPUNIT_ASSERT_THROW(expression, ExceptionType)         \
[&]() -> void {                                                 \
  bool cpputExceptionThrown_ = false;                           \
  try {                                                         \
     expression;                                                \
  } catch ( const ExceptionType & ) {                           \
     cpputExceptionThrown_ = true;                              \
  }                                                             \
                                                                \
  if ( cpputExceptionThrown_ )                                  \
     return;                                                    \
                                                                \
  CPPUNIT_NS::Asserter::fail(                                   \
                 "Expected exception: " #ExceptionType          \
                 " not thrown.",                                \
                 CPPUNIT_SOURCELINE() );                        \
}() 

However, there might be caveats, e.g. due to the lambda capturing the variables you use in the expression.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top