Search code examples
c++macrosassert

How to implement a standard-compliant assert macro with an optional formatted message?


What's the way to implement a standard-compliant assert macro with an optional formatted message?

What I have works in clang, but (correctly) triggers the -Wgnu-zero-variadic-macro-arguments warning if it is turned on (e.g. via -Wpedantic) when the macro is used without the optional message. Wandbox

#define MyAssert(expression, ...)                                      \
    do {                                                               \
        if(!(expression))                                              \
        {                                                              \
            printf("Assertion error: " #expression " | " __VA_ARGS__); \
            abort();                                                   \
        }                                                              \
    } while(0)

Solution

  • I have a solution which I'm not particularly proud of..

    We can obtain the first argument in plain form and as a string using:

    #define VA_ARGS_HEAD(N, ...) N
    #define VA_ARGS_HEAD_STR(N, ...) #N
    

    Note that in usage, in order to not get warnings, you should do VA_ARGS_HEAD(__VA_ARGS__, ) (with the extra ,) so that VA_ARGS_HEAD is never used with a single parameter (trick taken from StoryTeller's answer).

    We define the following helper function:

    #include <stdarg.h>
    #include <stdio.h>
    
    inline int assertionMessage(bool, const char *fmt, ...)
    {
        int r;
        va_list ap;
        va_start(ap, fmt);
        r = vprintf(fmt, ap);
        va_end(ap);
        return r;
    }
    

    When the assertion has a format string, the function would work with __VA_ARGS__ as is, however when the bool is the only argument, we're missing a format string. That's why we'll add another empty string after __VA_ARGS__ when invoking it:

    #define MyAssert(...)                                                          \
        do {                                                                       \
            if(!(VA_ARGS_HEAD(__VA_ARGS__, )))                                     \
            {                                                                      \
                printf("Assertion error: %s | ", VA_ARGS_HEAD_STR(__VA_ARGS__, )); \
                assertionMessage(__VA_ARGS__, "");                                 \
                abort();                                                           \
            }                                                                      \
        } while(0)
    

    Note that assertionMessage doesn't have printf in its name. This is deliberate and intended to avoid the compiler giving format-string related warnings for its invocations with the extra "" argument. The down-side for this is that we don't get the format-string related warnings when they are helpful.