cmacrosc-preprocessor

Detect a parenthesized macro argument in C


I want a macro to detect whether the passed arg is surrounded by parentheses or not, and call a different macro if so.

e.g.

#define FOO(obj) BAR obj
#define BAR(...) func(__VA_ARGS__)
#define BAR

If I call this with FOO((1, 2, 3)) it'd expand to func(1, 2, 3). But if I call it with FOO(1) it should expand to 1

I know that macro overloading isn't a thing in C, but I'm wondering if there's another way to achieve this behavior from FOO. I looked into several macro tricks but couldn't figure out a way to do this.

I'm working on a macro library and need this exact behavior.


Solution

  • I have just written this. This can be simplified, it's just the first version that I got on godbolt that worked. Should be enough to get you started.

    // if I call this with FOO((1, 2, 3)) it'd expand to func(1, 2, 3).
    // But if I call it with FOO(1) it should expand to 1
    
    #define ESCAPE2(...)  __VA_ARGS__
    #define ESCAPE(...)  ESCAPE2 __VA_ARGS__
    #define COMMA(...)     ,
    #define FIRST(T, ...)  T
    // from https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/
    #define ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
    #define HAS_COMMA(...) ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
    #define CONCAT(a, b)  a##b
    #define XCONCAT(a, b)  CONCAT(a, b)
    #define IF_1(a, b)  a
    #define IF_0(a, b)  b
    #define FOO(...)  \
            ESCAPE( \
                XCONCAT(IF_, HAS_COMMA(FIRST( COMMA __VA_ARGS__ () ))) \
                ((func __VA_ARGS__), (__VA_ARGS__)) \
            )
    
    FOO((1,2,3)) // func (1,2,3)
    FOO((1))  // func (1)
    FOO(1)   // 1
    FOO(1,2)   // 1,2
    

    This looks nicer. After converting __VA_ARGS__ to a comma, it's just a matter of getting a proper arg position.

    #define FOO_ESCAPE(...)  __VA_ARGS__
    #define FOO_COMMA(...)     ,
    #define FOO_FIRST(T, ...)  T
    #define FOO_THIRD(_1,_2,_3,...)  \
            FOO_ESCAPE _3
    #define FOO_XTHIRD(...)  \
            FOO_THIRD(__VA_ARGS__)
    #define FOO(...)  \
            FOO_XTHIRD(__VA_OPT__(FOO_FIRST(FOO_COMMA __VA_ARGS__)), \
                (func __VA_ARGS__), (__VA_ARGS__) )
    FOO((1,2,3))
    FOO((1))
    FOO()
    FOO(1)
    FOO(1,2)