cstringmacrosvariadic-macros

Concatenate __VA_ARGS__ into a single string using C macros


I'm trying to create a macro that expands into font effect escape codes for the terminal. Here's two and three argument examples:

#define FONT_FX2(arg1, arg2) "\x1b[" #arg1 ";" #arg2 "m"
#define FONT_FX3(arg1, arg2, arg3) "\x1b[" #arg1 ";" #arg2 ";" #arg3 "m"

Each argument is a font effect code stored either as macro constants or in an enum.

Is it possible to do this using variadic macro arguments?

Stringizing __VA_ARGS__ directly places commas between each argument, and I couldn't find any way to place semicolons between each argument. It would be nice if there was a to replace the commas with semicolons directly, like s/, /;/g using sed.


Solution

  • The C preprocessor is not a text manipulation system, but rather a token manipulation system. It cannot split, introspect, or modify tokens, but it can replace some token sequences with others, and it can form new tokens by pasting tokens together or by forming a string literal token representing the text of another token. The macro expansion facility has no recursion and no iteration (though limited iteration can be simulated). Although there is conditional compilation, there is no conditional evaluation in the context of macro expansion. There is certainly no sed-like character substitution anywhere in the preprocessor's arsenal.

    One can accomplish some surprising things despite those limitations. This gives a small taste, but does not push anywhere near the limits:

    // Helper macros:
    #define JOIN_4(a1, a2, a3, a4, ...) #a1 ";" #a2 ";" #a3 ";" #a4
    #define JOIN_3(a1, a2, a3, ...)     #a1 ";" #a2 ";" #a3
    #define JOIN_2(a1, a2, ...)         #a1 ";" #a2
    #define JOIN_1(a1, ...)             #a1
    #define SEMI_LIST(a1, a2, a3, a4, a5, ...) JOIN ## a5 (a1, a2, a3, a4)
    
    // Main macro
    // Use this with at least one and no more than four arguments:
    #define FONT_FX(...) "\x1b[" SEMI_LIST(__VA_ARGS__, _4, _3, _2, _1) "m"
    

    That determines the number of variable arguments, between one and four, and expands a different macro for each case, where the name of the argument-count-specific macro is formed via token pasting. That technique can be extended in the obvious way to a larger maximum number of arguments, but it cannot support an arbitrary number of arguments.