Sunday 5 January 2014

Macro variadic argument expansion on Windows.

Recently while trying to write some code I decided to use a variadic macro. For those familiar with the concept you'll know that they can be very powerful. Unfortunately I ran into a slight problem when using them. That problem being that the Microsoft compiler (and the Intel compiler for Windows) were both incorrectly expanding the variadic arguments (__VA_ARGS__). Or more to the point they both were completely failing to expand the arguments. This meant that it was not directly possible to get each element individually as the compiler was essentially treating the whole variadic argument as one long string (commas included). This is directly in violation of what the standards require and so both compilers are being non-compliant. The Microsoft compiler has never embraced C99 so this should not be to much of a surprise but Intels failure here is a bit more egregious.

Luckily there is a way to get around this. It is not quite as elegant as what works on other compilers but it will at least work on Windows while still building on other build chains. So the example I will give is a for-each macro. This uses the variadic expansion and then sends each argument element to a specific macro. The trick here was that each expansion had to be explicitly handled. This means that if you want to support a variadic macro that can take up to 10 arguments then you need to create 10 explicit variants of the expansion. This limits the maximum number of arguments that can be supported (by however many expansions you can be bothered to write) and is also not very elegant but at least it will work.

#define FE_0(P,X) P(X)
#define FE_1(P,X,X1) P(X), FE_0(P,X1)
#define FE_2(P,X,X1,X2) P(X), FE_1(P,X1,X2)
#define FE_3(P,X,X1,X2,X3) P(X), FE_2(P,X1,X2,X3)
#define FE_4(P,X,X1,X2,X3,X4) P(X), FE_3(P,X1,X2,X3,X4)
#define FE_5(P,X,X1,X2,X3,X4,X5) P(X), FE_4(P,X1,X2,X3,X4,X5)
#define FE_6(P,X,X1,X2,X3,X4,X5,X6) P(X), FE_5(P,X1,X2,X3,X4,X5,X6)
#define FE_7(P,X,X1,X2,X3,X4,X5,X6,X7) P(X), FE_6(P,X1,X2,X3,X4,X5,X6,X7)
#define FE_8(P,X,X1,X2,X3,X4,X5,X6,X7,X8) P(X), FE_7(P,X1,X2,X3,X4,X5,X6,X7,X8)
#define FE_9(P,X,X1,X2,X3,X4,X5,X6,X7,X8,X9) P(X), FE_8(P,X1,X2,X3,X4,X5,X6,X7,X8,X9)
#define GET_FE_IMPL(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,NAME,...) NAME
#define GET_FE(A) GET_FE_IMPL A
#define GET_FE_GLUE(x, y) x y
#define FOR_EACH_VA(P,...) GET_FE_GLUE(GET_FE((__VA_ARGS__,FE_9,FE_8,FE_7,FE_6,FE_5,FE_4,FE_3,FE_2,FE_1,FE_0)), (P,__VA_ARGS__))

This requires 3 pieces. The first is GET_FE_IMPL which is used to pass the variadic arguments to the explicit expansion macro (FE_0, FE_1 etc.). The second piece is GET_FE. This macro just passes directly to GET_FE_IMPL and on other compiler is completely redundant but on Windows it is used to add an extra level of indirection that forces the input parameters to be correctly 'stringified'. Remove it and everything will fail horribly. The final piece required to get this to work is the GET_FE_GLUE macro. This is perform the same task as GET_FE in that it is required to correctly pass the variadic arguments. Both of these are required in order to get around the bugs in the compilers.

This new macro can be used to create any new macro that should perform a desired operation on each of the input arguments. All that is required is to call FOR_EACH_VA and pass it a macro followed by the variadic arguments. The passed macro will then be called and passed each individual element in the argument list.

Example:
#define DO_SOMETHING(x) /*Insert whatever code you wish to operate on the element 'x'*/
//The definition of the new variadic macro
#define DO_SOMETHING_VA(...) FOR_EACH_VA(DO_SOMETHING,__VA_ARGS__)

With the above you should be able to create any variadic macro you want.

Example usage:
//Perform DO_SOMETHING on each of the 3 arguments
DO_SOMETHING_VA(element1,element2,element3)

If you want to support an additional number of input arguments (the above only supports a maximum of 10) then all you have to do is add more explicit expansion macros (e.g. FE_10, FE_11 etc.) and then add these in sequence to GET_FE_IMPL and to FOR_EACH_VA.

No comments:

Post a Comment