How to make a preprocessor macro greedy?
Asked Answered
S

2

4

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?

Sciatic answered 8/4, 2016 at 18:42 Comment(0)
F
3

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.

Farseeing answered 8/4, 2016 at 18:46 Comment(2)
I suspect it will cause problems, because the left argument X is put into the position of a declaration specifier. In a C and C++ style declaration, declaration specifiers do not take optional parentheses. Declarators do, however: int x; and int (x); are the same thing. (int) x; looks like a cast expression.Drippy
@Drippy Could you live with always putting parentheses around the argument that might contain template parameters, and with only supporting compilers that implement C99 variadic macros? If so, there is a hack that will work.Farseeing
D
5

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;
Drippy answered 8/4, 2016 at 18:48 Comment(3)
That is ugly. Can I always count on a non-greedy match? If so, I'm thinking to reverse the X and Y. The tricky point seems to be: what is well defined versus implementation defined. I need it to work on compilers for nearly all platforms and going back to the 1990s. Its a special kind of hell.Sciatic
The preprocessor breaks macro arguments lists on commas which are not wrapped in parentheses. (It counts parenthesis tokens so they balance). Square brackets, braces or angle brackets are not recognized; they provide no protection to the comma. It has little to do with greediness.Drippy
About portability, I used only C90 preprocessor syntax.Drippy
F
3

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.

Farseeing answered 8/4, 2016 at 18:46 Comment(2)
I suspect it will cause problems, because the left argument X is put into the position of a declaration specifier. In a C and C++ style declaration, declaration specifiers do not take optional parentheses. Declarators do, however: int x; and int (x); are the same thing. (int) x; looks like a cast expression.Drippy
@Drippy Could you live with always putting parentheses around the argument that might contain template parameters, and with only supporting compilers that implement C99 variadic macros? If so, there is a hack that will work.Farseeing

© 2022 - 2024 — McMap. All rights reserved.