We have the following preprocessor macro. Its used to help with Doxygen documentation because Doxygen has troubles with C++ and some template typedefs:
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(x, y) class y : public x {};
#else
# define DOCUMENTED_TYPEDEF(x, y) typedef x y;
#endif
It works great when X
is a non-template or has only one template parameter. However, if X
is a template with multiple parameters:
DOCUMENTED_TYPEDEF(Foo<R,S>,Bar);
Then it results in compile errors because the string is split into Foo<R
and S>,Bar
(and it does not generate the documentation).
How do I make a preprocessor macro greedy?
You're not gonna like this:
#define COMMA ,
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(x, y) class y : public x {};
#else
# define DOCUMENTED_TYPEDEF(x, y) typedef x y;
#endif
DOCUMENTED_TYPEDEF(Foo<R COMMA S>,Bar)
Test:
$ gcc -E comma-macro.c # 1 "comma-macro.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "comma-macro.c" # 9 "comma-macro.c" typedef Foo<R , S> Bar;
Macro argument lists are parsed for parentheses and commas before any substitution takes place. Then COMMA
is replaced in the x
argument, and x
is substituted into the macro body. AT that time, the argument breaking is done; it is not relevant that COMMA
got replaced with a comma punctuator token. However, that comma will separate arguments which occur in any macro calls generated by that macro, so if those have to be protected, you need something more crazy.
You can hide the COMMA
behind a function-like macro, say PAIR
:
#define COMMA ,
#define PAIR(A, B) A COMMA B
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(x, y) class y : public x {};
#else
# define DOCUMENTED_TYPEDEF(x, y) typedef x y;
#endif
DOCUMENTED_TYPEDEF(PAIR(Foo<R, S>), Bar)
At first glance it is more appealing, but there are probably downsides. It's more obfuscated. The reader wonders, is there semantics behind PAIR
? Whereas COMMA
looks too obtuse to have semantics, and its purpose is likely instantly obvious to anyone who has battle scars from fighting with the preprocessor.
About PAIR
, we may be able to hide it, and end up with a syntax like in Zwol's answer. But then we need multiple variants of the DOCUMENTED_TYPEDEF
.
Also, by the way, let's drop the useless COMMA
which is not needed on the right hand side of a macro:
#define PAIR(A, B) A, B
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF_2(x2, y) class y : public PAIR x2 {};
#else
# define DOCUMENTED_TYPEDEF_2(x2, y) typedef PAIR x2 y;
#endif
DOCUMENTED_TYPEDEF_2((<R, S>), Bar)
$ gcc -std=c90 -E -Wall -pedantic comma-macro.c # 1 "comma-macro.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "comma-macro.c" # 11 "comma-macro.c" typedef <R, S> Bar;
This looks like it may be doable with C99 style variadic macros. However, that may violate the portability requirement discussed in the comments, not to mention that this is C++. For the sake of future visitors:
#define PNEUMATIC_COMMA_GUN(A, ...) A, ## __VA_ARGS__
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(xv, y) class y : public PNEUMATIC_COMMA_GUN xv {};
#else
# define DOCUMENTED_TYPEDEF(xv, y) typedef PNEUMATIC_COMMA_GUN xv y;
#endif
DOCUMENTED_TYPEDEF((<R, S, T, L, N, E>), Bar)
$ gcc -std=c99 -E -Wall -pedantic comma-macro.c # 1 "comma-macro.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "comma-macro.c" # 9 "comma-macro.c" typedef <R, S, T, L, N, E> Bar;
There is no way to change how the preprocessor parses the arguments to a macro. Commas that are not within parentheses always separate macro arguments.
What you may be able to do is
DOCUMENTED_TYPEDEF((Foo<R,S>), Bar);
but of course this only works if it's okay for the inner parentheses to appear in the expansion of the macro. I don't remember off the top of my head if this will cause problems in the contexts you are showing.
If it's OK to require C99 variadic macros, you can use them to get rid of the extra parentheses:
#define STRIP_PARENS(...) __VA_ARGS__
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(x, y) class y : public STRIP_PARENS x {};
#else
# define DOCUMENTED_TYPEDEF(x, y) typedef STRIP_PARENS x y;
#endif
DOCUMENTED_TYPEDEF((Foo<R,S>), Bar);
but now you always have to put an extra pair of parentheses around the first argument to DOCUMENTED_TYPEDEF.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With