1

Background

I am working on an existing codebase which uses a macro pattern to generate boilerplate methods similar to this:

START_MAP()
  MAP_ENTRY(a)
  MAP_ENTRY(b)
  // .....
  MAP_ENTRY(z)
END_MAP()

I am re-implementing the code that these macros generate, but I can not touch this pattern because that would require large refactors. I need to expand this pattern into a macro (which I define) which we will call NEW_IMPLEMENT which is a variadic macro that is called like so: NEW_IMPLEMENT(a, b, ..., z).

Problem

How can I redefine START_MAP, MAP_ENTRY, and END_MAP so that the pattern as it currently exists expands to NEW_IMPLEMENT(a, b, ..., z)?

What I have tried so far

#define NEW_IMPLEMENT(...) ...
#define START_MAP NEW_IMPLEMENT(
#define MAP_ENTRY(x) x,
#define END_MAP )

This throws a preprocessor error, however: error: unterminated argument list invoking macro "NEW_IMPLEMENT"

Jerfov2
  • 5,264
  • 5
  • 30
  • 52
  • 1
    I'm afraid that the C preprocessor cannot do this. Consider to use another preprocessor like for example M4, or write your own specific preprocessor. – the busybee Aug 04 '22 at 06:11
  • 1
    So solution must work for both C and C++? – hyde Aug 04 '22 at 07:04
  • writing macros that expand to `NEW_IMPLEMENT(a, b, ..., z)` isnt the problem, but I suppose you want to have `NEW_IMPLEMENT(a, b, ..., z)` also expanded. Whats the initial problem that led you to think this would be a solution? If you cannot do large refactors, why change the original macros? Can you not get desired end result by changing definitions of `START_MAP`, `MAP_ENTRY` and `END_MAP` ? – 463035818_is_not_an_ai Aug 04 '22 at 07:16
  • 1
    looks like a [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – 463035818_is_not_an_ai Aug 04 '22 at 07:16
  • 1
    asked differently: What should `NEW_IMPLEMENT(a,b,...,z)` expand to? How does this differ from what the old macros expanded to? – 463035818_is_not_an_ai Aug 04 '22 at 07:17
  • please add some context to the question. And please choose one language. I dont know C, but in C++ there might be other solutions to achieve the same result (provided you tell us what the end result should be) – 463035818_is_not_an_ai Aug 04 '22 at 07:19
  • `I can not touch this pattern because that would require large refactors` But macro expansion is just a simple text replacement anyway, so the refactor should is trivial. Why would it be "large"? Why not do it? – KamilCuk Aug 04 '22 at 07:19
  • @hyde solutions that work for both are best, but mainly c++ – Jerfov2 Aug 04 '22 at 12:50
  • @463035818_is_not_a_number I can get a suboptimal result by interleaving the results of `START_MAP`, `MAP_ENTRY`, and `END_MAP`, but I would get better results and cleaner code if I could squeeze it all into one macro – Jerfov2 Aug 04 '22 at 12:52
  • @463035818_is_not_a_number What `NEW_IMPLEMENT` will expands is basically two methods and and one field definition. Something like this: `some_field = a + b + ... + z; void method1() { do(a); do(b); ...; do(z); } void method2(do2(a); do(b); ...; do(z); }` So basically where a, b, z are used multiple times in sequence with 3 different patterns separating them. This is why I am trying to squeeze it into one macro call. – Jerfov2 Aug 04 '22 at 13:00

1 Answers1

1

You can make the pattern expand to NEW_IMPLEMENT(stuff) text, but the resulting macro will not be expanded - C preprocessor does not rescan results from multiple macro expansions together.

You can "join" them, by wrapping everything in another macro call which will force another pass over all the results. And also you need to pass the paren as a token, not literally, so that you don't get unterminated call.

#define CALL(...)   __VA_ARGS__

#define PAREN (
#define START_MAP()   NEW_IMPLEMENT PAREN
#define END_MAP()   )
#define MAP_ENTRY(a)  a,

#define NEW_IMPLEMENT(...)  "Hello: " #__VA_ARGS__

CALL(
START_MAP()
  MAP_ENTRY(a)
  MAP_ENTRY(b)
  // .....
  MAP_ENTRY(z)
END_MAP()
)

But overall, I do not understand. I would prefer to refactor the code with a simple sed 's/START_MAP()/NEW_IMPLEMENT(/; s/MAP_ENTRY(a)/a,/; s/END_MAP()/)/' for code readability and maintainability. I do not think "not touching ancient code" is a good enough reason for making the codebase more convoluted.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks for the answer! It helps me get closer! To answer "I do not think 'not touching ancient code' is a good enough reason for making the codebase more convoluted": the existing patterns is used around 100 times in fairly critical parts of the (open-source) codebase. Changing every single instance of the pattern would entail a unnecessarily large git diff and more review work for not adding anything of substance. – Jerfov2 Aug 04 '22 at 13:07
  • Yes, exactly. I do not think "creating a large git diff" and "more review work" should ever be considered as an argument, that's my point. Git is a tool to be used, review is something to be done, code is to be changed. – KamilCuk Aug 04 '22 at 13:25
  • I do not think a code tool that "creates a large diff" should hinder your ability to write good code. In that case, you should rather ditch `git`, then allow yourself writing bad code.... Maybe this makes the point clearer. Overall the goal is _the code_, not tools around it, code review and git should _help_ the code. Usually in "ancient code" testing and senior programmers are the usual stoppers. – KamilCuk Aug 04 '22 at 13:36
  • "Usually in "ancient code" testing and senior programmers are the usual stoppers." That's the thing: the "senior" programmers in this project don't want the pattern changed. This restriction is part of the problem statement, sorry. – Jerfov2 Aug 04 '22 at 15:00