I'm trying to clean up some legacy code that contains hundreds of functions with bodies that look like this:
void functionWithSideEffect(Foo* foo)
{
if (foo)
{
// Use `foo' to warm the planet
...
}
}
Obviously, silently failing if a precondition check fails isn't the best idea, so I'd like to refactor this to:
int functionWithSideEffect(Foo* foo)
{
RETURN_IF_FALSE(foo != NULL, "foo is NULL in functionWithSideEffect!");
// Use `foo' to warm the planet
...
}
The following macro seems to work fine for functions that don't return a value:
#define RETURN_IF_FALSE(cond, msg) \
do { \
if (!(cond)) { \
LOG("%s\n", (msg)); \
assert((cond)); \
} \
return; \
} while(0)
And it has the following desirable properties:
- It is clear and succinct in usage
- It does not silently fail
- It will crash in debug builds, but attempt to soldier on in release builds
(Admittedly, for functions returning void, soldiering on in release builds may not always be 'desirable'.)
For functions that do return a value, this macro does the trick:
#define RETURN_VALUE_IF_FALSE(cond, msg, retval ) \
do { \
if (!(cond)) { \
LOG("%s\n", (msg)); \
assert((cond)); \
} \
return(retval); \
} while(0)
My question is: is it possible to write a single RETURN_IF_FALSE macro to handle both void functions and functions returning a value? I sat down to attempt something using a varargs macro and quickly discovered I'm not very good at writing complex macros. I started out with this test program:
#include <stdio.h>
#include <assert.h>
#define RETURN_IF_FALSE(cond, msg, ... ) \
do { \
if (!(cond)) { \
fprintf(stderr, "%s\n", (msg)); \
assert((cond)); \
} \
return (##__VA_ARGS__); \
} while(0)
int main()
{
RETURN_IF_FALSE(1 < 0, "1 is not less than 0!", -1);
return 0;
}
Perhaps not surprisingly, it generated the following compile error:
g++ macro_test.cpp -o macro_test
macro_test.cpp:10:14: error: pasting "(" and "-" does not give a valid preprocessing token
return (##__VA_ARGS__); \
^
macro_test.cpp:16:5: note: in expansion of macro ‘RETURN_IF_FALSE’
RETURN_IF_FALSE(1 < 0, "1 is not less than 0!", -1);
^
Is it even possible to cover both cases with a single macro? I'm using gcc 4.8.1 on Linux. (I can compile with -std=c++11, if it helps...)
UPDATE: To bring this full-circle, here's the implementation I ultimately wound up with based on @Turix's answer and a suggestion from @Deduplicator to move the assert() call up to avoid a double evaluation of the conditional in the 'sunny day' case:
#define RETURN_IF_FALSE(cond, ... ) \
do { \
if (!(cond)) { \
const char* msg = \
"Pre-condition '" #cond "' not met, returning " #__VA_ARGS__ "..."; \
LOG("%s\n", msg); \
assert((cond)); \
return __VA_ARGS__; \
} \
} while(0)
(I decided it wasn't really all that necessary/useful to allow setting of a 'free form' message string, so I just generated a canned one from the condition...)
##? Leave it out, and it will compile. - dypcondonce. - Deduplicatorelseclause to theif(foo)statement and make noise about the failure there? - celtschk