Saturday 21 May 2011

static_assert and the C++ Preprocessor

With a title like that, you know you're in for a bit of geekiness.

With C++0x we now have proper static asserts. Hurrah! Except I don't like them. Their syntax is:

static_assert(sizeof(int) < 5, "Integers too big");

To me, the second argument is extra fluff that should be optional, but isn't. Perhaps we can get around it with preprocessor macros:

STATIC_ASSERT(sizeof(int) < 5, "Integers too big");
STATIC_ASSERT(sizeof(int) < 5);

But how do we overload preprocessor macros? With the help of variadic macros it is (just about) possible to overload based on the number of arguments. Ironically, this is much easier with the C99 preprocessor than with the current C++ one; all the solutions for C++ that I've seen need tweaking for different compilers. The code below works for Microsoft C++ 2010.

As an aside, Jens Gustedt has produced an amazing (and instructive) set of C99 preprocessor macros called P99. Well worth a look.

But for our C++ case, we want to construct something along the lines of:

#define STATIC_ASSERT(condition, ...) \
    if (__VA_ARGS__ is empty) then \
        static_assert(condition, #condition) \
    else \
        static_assert(condition, __VA_ARGS__)

There are two problems here: (a) how to determine if there are additional arguments, and (b) achieving an if-then-else structure in a macro that's fully resolved by the preprocessor.

The first problem can be solved using a technique I first saw in a reply to a question on stackoverflow:

#define CHILLIANT_LITERAL(...) __VA_ARGS__
#define CHILLIANT_VA_PICK__(a,b,c,d,e,f,g,h,i,j,...) j
#define CHILLIANT_VA_PICK_(a) \

    CHILLIANT_LITERAL(CHILLIANT_VA_PICK__)a
#define CHILLIANT_VA_PICK(a,b,...) \

    CHILLIANT_VA_PICK_((a##__VA_ARGS__##b,a##b))
#define CHILLIANT_VA_EMPTY__YTPME_AV_TNAILLIHC 0,0,0,0,0,0,0,0,0,1
#define CHILLIANT_VA_EMPTY(...) \

    CHILLIANT_VA_PICK(CHILLIANT_VA_EMPTY_, \
        _YTPME_AV_TNAILLIHC,__VA_ARGS__)

How this actually works is left as an exercise for the reader, but the bottom line is that "CHILLIANT_VA_EMPTY(...)" called with no arguments resolves to the token "1", whilst one or more arguments causes it to resolve to "0". The above code snippet only works for up to nine arguments, but it is trivial to expand to a greater number.

For completeness, here's the macro to return the count of the number of arguments:

#define CHILLIANT_VA_COUNT__TNUOC_AV_TNAILLIHC 9,8,7,6,5,4,3,2,1,0
#define CHILLIANT_VA_COUNT(...) \

    CHILLIANT_VA_PICK(CHILLIANT_VA_COUNT_, \
        _TNUOC_AV_TNAILLIHC,__VA_ARGS__)

Groovy, huh?

The second problem, generating the if-then-else semantic, is made difficult because we want it to resolve during the preprocessor phase, not during compilation. However, if we assume the condition is only ever the token "0" or "1" (which it is) the problem can be solved with careful identifier concatenation:

#define CHILLIANT_IGNORE(...)
#define CHILLIANT_IF_0(...) CHILLIANT_LITERAL
#define CHILLIANT_IF_1(...) __VA_ARGS__ CHILLIANT_IGNORE
#define CHILLIANT_IF(condition) \

    CHILLIANT_LITERAL(CHILLIANT_IF_)##CHILLIANT_LITERAL(condition)

This is used as follows:

CHILLIANT_IF(condition)(then_clause)(else_clause)

Now, we can construct the definition of "STATIC_ASSERT()":

#define STATIC_ASSERT(condition, ...) \
    static_assert(condition, \
        CHILLIANT_IF(CHILLIANT_VA_EMPTY(__VA_ARGS__)) \
            (#condition) \
            (__VA_ARGS__))

This does seem to be a lot of trouble to go to just to make the second argument of a static assert optional, but now you also have some of the basic building blocks to really go to town with the preprocessor.

2 comments :

  1. Nice application of empty argument detection to C++. I am not sure that you are not using some compiler extension to resolve this case

    STATIC_ASSERT(a < b);

    because as I understand (for C99 this would be so) STATIC_ASSERT expects two or more arguments. I'd think that it would be better to distinguish if you receive two arguments or less. The tricks should work out the same, just with some different offsets in the lists.

    Also a "disadvantage" of C++ is that the testing expression can easily contain commas, e.g in template arguments. Such an expression should probably always be protected by an extra pair of parenthesis arround them.

    I always thought that C++ people were not too much interested in such preprocessor tricks. Now that the preprocessor converges between the newer C++1x and C1x it would perhaps be worth considering having a well defined part of P99 that would also work for C++.

    ReplyDelete
  2. Yes, I wasn't being too rigorous in my approach; I'm afraid I don't have the time nor the will-power to be fastidious (at everything!). I just wanted to make some notes on a feature that I actually use very, very rarely. But when it IS wanted, it's unbelievably powerful.

    However, I'm using shouty/capital 'STATIC_ASSERT' for the macro, not quiet/lowercase 'static_assert', so I don't think I fully comprehend your concerns about the number of arguments.

    Thanks for the heads-up on the convergence of the preprocessors for C and C++. That can only be a good thing.

    ReplyDelete