c++cmacrosc-preprocessor

Comma in C/C++ macro


Say we have a macro like this

#define FOO(type,name) type name

Which we could use like

FOO(int, int_var);

But not always as simply as that:

FOO(std::map<int, int>, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2

Of course we could do:

 typedef std::map<int, int> map_int_int_t;
 FOO(map_int_int_t, map_var); // OK

which is not very ergonomic. Plus type incompatibilities have to be dealt with. Any idea how to resolve this with macro ?


Solution

  • Because angle brackets can also represent (or occur in) the comparison operators <, >, <= and >=, macro expansion can't ignore commas inside angle brackets like it does within parentheses. (This is also a problem for square brackets and braces, even though those usually occur as balanced pairs.) You can enclose the macro argument in parentheses:

    FOO((std::map<int, int>), map_var);
    

    The problem is then that the parameter remains parenthesized inside the macro expansion, which prevents it being read as a type in most contexts.

    A nice trick to workaround this is that in C++, you can extract a typename from a parenthesized type name using a function type:

    template<typename T> struct argument_type;
    template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
    #define FOO(t,name) argument_type<void(t)>::type name
    FOO((std::map<int, int>), map_var);
    

    Because forming function types ignores extra parentheses, you can use this macro with or without parentheses where the type name doesn't include a comma:

    FOO((int), int_var);
    FOO(int, int_var2);
    

    In C, of course, this isn't necessary because type names can't contain commas outside parentheses. So, for a cross-language macro you can write:

    #ifdef __cplusplus__
    template<typename T> struct argument_type;
    template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
    #define FOO(t,name) argument_type<void(t)>::type name
    #else
    #define FOO(t,name) t name
    #endif