0

I want to generate several pieces of code for several different values using the C++ preprocessor. I entered all of these values into a table generation macro. However, some parts of the code only have to be generated for certain (disjunct) subsets of the values. Hence, I would like to have small table generation macros and then merge them together into a large one (for the code that has to be generated for all of them). This part works flawlessly so far.

However, I require to use a comma as a seperator for some parts of the code (precisely to generate an enum). Unfortunately, this comma is replaced too early (i.e. before the function call of the macro is replaced). Here comes an example which should illustrate my problem:

#include <iostream>

#define A(F, SEP) F(one) SEP F(two)
#define B(F, SEP) F(three) SEP F(four)
#define C(F, SEP) A(F, SEP) SEP B(F, SEP)

#define SOME_F(x) x  
#define SOME_SEP ,
enum {C(SOME_F, SOME_SEP)} ENUM;

int main()
{
    #define ANOTHER_F(x) std::cout << #x << std::endl;
    #define ANOTHER_SEP 
    A(ANOTHER_F, ANOTHER_SEP)
    return 0;
}

Here I want to generate an enum containing all values (it's unused in the example but in my actual code I require it) and print all values of the subset A. If I try to compile this example, it fails with the error error: macro "A" passed 3 arguments, but takes just 2 (same message for B). The reason for this seems to be, that the SOME_SEP macro is replaced before A is substituted (and an attempt to use A(F, ,) is made).

A rather simple (but ugly) fix for this is to replace the SEP argument to be a function-like instead of an object-like macro (one has to add parentheses to each call though). However, I would like to solve this using an object-like macro. During my web search I discovered a lot of people with a similar problem, but they wanted to pass a template-type (and hence can resolve the issue by using braces (which doesn't work for the comma as a seperator)).

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 2
    The answer is: you don't do any of this. Modern C++ has little requirements for macros; if any they are short, brief, and quite limited in scope. Extensive use of macros often leads to buggy, hard to diagnose, and difficult to comprehend code, that often doesn't even look like C++, but some other strange language. Macros are only popular on a bunch of useless coding puzzle sites, which encourage bad programming practices and don't actually have any other intrinsic value to them. – Sam Varshavchik Dec 20 '21 at 12:01
  • I do not understand - what is the end goal here? Why do you act like an enum should be an array? If you want to apply a function over multiple items, declare an array, and then use that array. Overall, this usage is just confusing and I believe you will end with unmaintainable code. This seems like XY question - how do you intent to use such code? What problem does it solve? – KamilCuk Dec 24 '21 at 12:45

1 Answers1

0

Just use brackets and then remove them.

#include <iostream>

#define EXP(...)  __VA_ARGS__

#define A(F, SEP) F(one) EXP SEP F(two)
#define B(F, SEP) F(three) EXP SEP F(four)
#define C(F, SEP) A(F, SEP) EXP SEP B(F, SEP)

#define SOME_F(x) x
enum {C(SOME_F, (,))} ENUM;

int main() {
    #define ANOTHER_F(x) std::cout << #x << std::endl;
    A(ANOTHER_F, ())
}

Overall, this is odd usage and looks odd. Consider using BOOST_PP_SEQ_FOR_EACH and similar FOREACH_* macros. It's odd to define list of elements inside a macro to iterate over - I would expect it to be defined outside and passed as parameter. Like:

#define LIST1  (one)(two)

#define CALLBACK(x)  x
SUPER_FOREACH(LIST1, CALLBACK, (,))

Also see How to convert an enum type variable to a string? if you want to stringify an enum.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111