2
votes

I know that in expanding a function-like preprocessor macro, the # and ## tokens in the top-level substitution list essentially act "before" any macro expansions on the argument. For example, given

#define CONCAT_NO_EXPAND(x,y,z) x ## y ## z
#define EXPAND_AND_CONCAT(x,y,z) CONCAT_NO_EXPAND(x,y,z)
#define A X
#define B Y
#define C Z

then CONCAT_NO_EXPAND(A,B,C) is the pp-token ABC, and EXPAND_AND_CONCAT(A,B,C) is the pp-token XYZ.

But what if I want to define a macro that expands just some of its arguments before pasting? For example, I would like a macro that allows only the middle of three arguments to expand, then pastes it together with an exact unexpanded prefix and an exact unexpanded suffix, even if the prefix or suffix is the identifier of an object-like macro. That is, if again we have

#define MAGIC(x,y,z) /* What here? */
#define A X
#define B Y
#define C Z

then MAGIC(A,B,C) is AYC.

A simple attempt like

#define EXPAND(x) x
#define MAGIC(x,y,z) x ## EXPAND(y) ## z

results in an error 'pasting ")" and "C" does not give a valid preprocessing token". This makes sense (and I assume it's also producing the unwanted token AEXPAND).

Is there any way to get that sort of result using just standard, portable preprocessor rules? (No extra code-generating or -modifying tools.)

If not, maybe a way that works on most common implementations? Here Boost.PP would be fair game, even if it involves some compiler-specific tricks or workarounds under the hood.

If it makes any difference, I'm most interested in the preprocessor steps as defined in C++11 and C++17.

1
"then MAGIC(A,B,C) is AYC." Well, let's agree that asking for magic is at least off-topic. - πάντα ῥεῖ
@πάνταῥεῖ No, I disagree, this way of using "magic" (the word) to represent the solution is a good way. That way I use magic regularly at my job. - Yunnosch

1 Answers

2
votes

Here's a solution:

#define A X
#define B Y
#define C Z
#define PASTE3(q,r,s) q##r##s
#define MAGIC(x,y,z,...) PASTE3(x##__VA_ARGS__,y,__VA_ARGS__##z)
MACRO(A,B,C,)

Note that the invocation "requires" another argument (see below for why); but:

  • MACRO(A,B,C) here is compliant for C++20
  • MACRO(A,B,C) will "work" in many C++11/C++17 preprocessors (e.g., gnu/clang), but that is an extension not a C++11/C++17 compliant behavior
I know that in expanding a function-like preprocessor macro, the # and ## tokens in the top-level substitution list essentially act "before" any macro expansions on the argument.

To be more precise, there are four steps to macro expansion:

  • argument identification
  • argument substitution
  • stringification and pasting (in an unspecified order)
  • rescan and further replacement

Argument identification associates parameters in the macro definition with arguments in an invocation. In this case, x associates with A, y with B, z with C, and ... with a "placemarker" (abstract empty value associated with a parameter whose argument has no tokens). For C++ preprocessors up to C++20, use of a ... requires at least one parameter; since C++20's addition of the __VA_OPT__ feature, use of the ... in an invocation is optional.

Argument substitution is the step where arguments are expanded. Specifically, what happens here is that for each parameter in the macro's replacement list (here, PASTE3(x##__VA_ARGS__,y,__VA_ARGS__##z)), where said parameter does not participate in a paste or stringification, the associated argument is fully expanded as if it appeared outside of an invocation; then, all mentions of that parameter in the replacement list that do not participate in stringification and paste are replaced with the expanded result. For example, at this step for the MAGIC(A,B,C,) invocation, y is the only mentioned qualifying parameter, so B is expanded producing Y; at that point we get PASTE3(x##__VA_ARGS__,Y,__VA_ARGS__##z).

The next step applies pastes and stringification operators in no particular order. Placemarker's are needed here specifically because you want to expand the middle and not the end, and you don't want extra stuff; i.e., to get A to not expand to X, and to stay A (as opposed to changing to "A"), you need to avoid argument substitution specifically. a.s. is avoided in only two ways; pasting or stringifying, so if stringification doesn't work we have to paste. And since you want that token to stay the same as what you had, you need to paste to a placemarker (which means you need one to paste to, which is why there's another parameter).

Once this macro applies the pastes to the "placemarkers", you wind up with PASTE3(A,Y,C); then there is the rescan and further replacement step, during which PASTE3 is identified as a macro invocation. Fast forwarding, since PASTE3 pastes its arguments, a.s. doesn't apply to any of them, we do the pastes in "some order" and we wind up with AYC.

As a final note, in this solution I'm using a varying argument to produce the placemarker token precisely because it allows invocations of the form MACRO(A,B,C) in at least C++20. I'm left-pasting that to z because that makes the addition at least potentially useful for something else (MAGIC(A,B,C,_) would use _ as a "delimiter" to produce A_Y_C).