JrDebugLogger is a very nice debug logging library. Much of it's functionality is implemented through macros to allow it to be selectively left out when compiling. Along the way the author has had some interesting problems to solve, and this post is about one of them.
Assume we use the following macro:
#define DEBUGOUT if (debug_on) debug_stream
to allow us to perform debug logging with a stream-like interface. We can then do:
DEBUGOUT << "hello";
which expands to:
if (debug_on) debug_stream << "hello";
Now if the compiler knows that
debug_on is false, it can leave out all code related to the debug logging, since it knows it will never be called. If it does not know the value at compile time, the resulting code will contain a very fast check around the call, allowing debug logging to be turned on and off dynamically with little performance overhead.
There is, however, an insidious bug lurking in the corner, waiting to jump at the user. Can you spot the problem? Think about it for a minute or two before reading on.
Consider this use:
if (i > limit)
DEBUGOUT << "i too big";
else
do_computation(i);
it expands to:
if (i > limit)
if (debug_on) debug_stream << "i too big";
else
do_computation(i);
This is valid C++, and compiled without warnings on the three compilers I tried. But who does that else belong to?
Let's see what the standard says:
"An else is associated with the lexically nearest preceding if that is allowed by the syntax."
This is from the C99 standard (6.8.4.1p3), which was the most clear, however statements to the same effect are present in the C++ standards.
So the above is equivalent to:
if (i > limit)
{
if (debug_on)
debug_stream << "i too big";
else
do_computation(i);
}
which was of course not the intention.
So how can we solve this without giving up the nice properties of the if? The simple solution is to give the if in the macro its own else:
#define DEBUGOUT if (!debug_on) ; else debug_stream
We now get the expansion:
if (i > limit)
if (!debug_on) ; else debug_stream << "i too big";
else
do_computation(i);
and the compiler will correctly associate the users else with his if. So it is equivalent to:
if (i > limit)
{
if (!debug_on)
;
else
debug_stream << "i too big";
} else {
do_computation(i);
}
Thanks to Jesse for the nice topic.