2

Is it legal to write recursive macros with __VA_OPT__?

GCC and Clang appear to not replace recursively, but I'm not sure if it's intentional (as __VA_OPT__ support is very recent).

C++ spec (§19.3.1/3: __VA_OPT__):

Otherwise, the replacement consists of the results of the expansion of content as the replacement list of the current function-like macro before rescanning and further replacement

Does the highlighted section above mean recursion isn't possible?


For example, to add a list of variadic macro parameters:

#define RECURSE(mFIRST, ...) + mFIRST __VA_OPT__(RECURSE(__VA_ARGS__))

int main(int argc, char const* const argv[])
    {
        return 1 RECURSE(2, 3, 4);
        // Expected result: "return 1 + 2 + 3 + 4;"
    }

Both GCC and Clang generate RECURSE in their post-preprocessing.

// NOTE: Below is the output from g++ -E
int main(int argc, char const* const argv[])
 {
  return 1 + 2 RECURSE(3, 4);
 }

NOTE: If this is possible, more complex variadic macros could be written fairly easy, such as concatenate, as you can create a custom __VA_NO_OPT__ from __VA_OPT__, which lets you provide completely separate code for 1 and 2+ parameters.

xaxazak
  • 748
  • 4
  • 16
  • 4
    [Recursive macros don't work in general.](https://gcc.gnu.org/onlinedocs/cpp/Self-Referential-Macros.html) – user2357112 Jul 04 '18 at 05:18
  • 1
    Is that only because they would always recurse infinitely? Because with `__VA_OPT__` that's no longer true. – xaxazak Jul 04 '18 at 05:33
  • 1
    I guess it's due to this: 19.3.4/2: Rescanning and further replacement. "If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file’s preprocessing tokens), it is not replaced". – xaxazak Jul 04 '18 at 05:40
  • @user2357112 - oops, just noticed your comment was a link, which explains it, thanks. (Pity, I wonder if that might get amended now). – xaxazak Jul 04 '18 at 06:03
  • 1
    It's unlikely to get amended. C++ tries very hard to get rid of the needs to use macros each revision. It can't, for various reasons, but recursion is unlikely to be added. `__VA_OPT__` is a tiny convenience feature. Recursive macros are one mighty change. – StoryTeller - Unslander Monica Jul 04 '18 at 06:08
  • Why don't you use variadic templates instead? – JVApen Jul 04 '18 at 06:20
  • @JVApen I do lots, like 50x more. But sometimes macros are still the best answer. Often it's related to identifier names. One example - populating a constexpr lookup table for vulkan formats. The format enum identifier names and format properties are linked and I can remove about 95% of the code using macros. – xaxazak Jul 04 '18 at 06:26
  • 1
    @NicolBolas: AFAICT `__VA_OPT__` is a c++2a feature, not a c++17 feature, hence the tag rollback. Apologies if I'm somehow misinformed. – xaxazak Jul 04 '18 at 17:24

1 Answers1

3

The answer is yes! To the extent that you could already have recursive macros in C, you can do it in C++20 and __VA_OPT__ makes some things way, way nicer.

Here's an example (with explanation) of what you can do: define a FOR_EACH macro that applies a macro to a bunch of arguments and is way nicer than the horrible things you had to do prior to __VA_OPT__:

#define PARENS () // Note space before (), so object-like macro

#define EXPAND(arg) EXPAND1(EXPAND1(EXPAND1(EXPAND1(arg))))
#define EXPAND1(arg) EXPAND2(EXPAND2(EXPAND2(EXPAND2(arg))))
#define EXPAND2(arg) EXPAND3(EXPAND3(EXPAND3(EXPAND3(arg))))
#define EXPAND3(arg) EXPAND4(EXPAND4(EXPAND4(EXPAND4(arg))))
#define EXPAND4(arg) arg

#define FOR_EACH(macro, ...)                                    \
  __VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, a1, ...)                         \
  macro(a1)                                                     \
  __VA_OPT__(FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
#define FOR_EACH_AGAIN() FOR_EACH_HELPER

FOR_EACH(F, a, b, c, 1, 2, 3)   // => F(a) F(b) F(c) F(1) F(2) F(3)
user3188445
  • 4,062
  • 16
  • 26
  • I actually use something like this, with F often being a user-defined string literal, so MACRO("dir1", "dir2", "file") becomes "dir1"_X, "dir2"_X, "file"_X. Useful (clearer) when creating constexpr arrays holding each directory name in a path, especially when the UDL name is actually longer than _X. Max 64 items though currently, so still feels a bit hacky. – xaxazak Jun 09 '21 at 02:35
  • 1
    @xaxazak The code above accepts up to 342 arguments, which is better than 64, and probably enough for anything reasonable. If not, one more line of code for `EXPAND5` and you would surely have enough. – user3188445 Jun 09 '21 at 07:21
  • Your exponential technique's better than mine (I will adapt it, thanks), but unfortunately, any arbitrary maximum will still feel hacky. There's also a tradeoff with compilation time. If you're specifying 100s of paths each requiring 1000s of macro replacements, and the compiler is tracking these for debugging, it might become noticeable. – xaxazak Jun 10 '21 at 05:40
  • Also, this makes a good case for something like `__VA_SEP__()` which would act like `__VA_ARGS__` but use `` instead of a comma. This might eliminate much of the need (want) for recursion. – xaxazak Jun 10 '21 at 05:48
  • @xaxazak `__VA_SEP__` is less general than `FOR_EACH`. If they were going to facilitate this kind of macro, the best thing would be just to build `FOR_EACH` (or `__FOR_EACH__` or something) straight into the preprocessor. – user3188445 Jun 14 '21 at 01:22
  • True, that would be best. Only if they're unwilling to make macros that powerful (maybe worries about overuse) would it make sense to add __VA_SEP__ instead of __FOR_EACH__. – xaxazak Jun 15 '21 at 20:30