Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a preprocessor macro greedy?

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?

like image 965
jww Avatar asked Feb 07 '23 07:02

jww


2 Answers

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;
like image 147
Kaz Avatar answered Feb 16 '23 03:02

Kaz


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.

like image 43
zwol Avatar answered Feb 16 '23 03:02

zwol