0
votes

I am trying to define a C++ struct with variable members by a macro as part of an introspection metadata system. Given that I am creating a struct definition I cannot use compile-time templates (right?). Instead I create a variadic macro that should accept tuples of (type, name) but I have had problems in writing a macro that would expand and join the tuple with a symbol to achieve something like

JOIN(.,(a,A)) -> a.A or JOIN(&,(b,B)) -> b&B.

I am working in Visual Studio 2017 and part of my confusion may be the inconsistency in the VA_ARGS expansion in MSVC and GCC: MSVC++ variadic macro expansion

My approach has been to write an unpacking macro that simply strips the parenthesis from the tuple and a join macro that would join the arguments by the desired string:

#define UNPACK(a, b) a, b
#define JOINAB(a, b, S) a S b
#define JOINTUPLE(S, ab) JOINAB(UNPACK ab, S)

But this does not work as it seems the macro is not evaluated in the right order. I have then tried to explicitly expand the arguments, e.g. #define EXPAND(args) args, but to no luck.

I have finally found a workaround by embedding the argument parenthesis in the unpacking, thereby 'forcing' an order of evaluation:

#define EXPAND(...) __VA_ARGS__
#define UNPACK(a, b) (a, b
#define JOINAB(a, b, S) a S b
#define JOINTUPLE(S, ab) EXPAND(JOINAB UNPACK ab, S))

which works, but seems extremely hacky...

My questions are

  1. Is there a proper way to achieve the evaluation of the unpacked result?
  2. Why do I need the EXPAND? Without it the expression JOINTUPLE(:,(a,B)) resolves to JOIN (a, B, :) but why is this not further processed to a : B?
  3. Could there be a way to solve it with token-pasting operator? S would only exist in 3 variants.
1
One more EXPAND does the trick, although I can't explain in detail how exactly it falls together.Quentin
Sure you can use compile-time templates. In C++, struct means exactly the same as class.TonyK
@Quentin Spot on. You pulled the parenthesis inside the EXPAND which had the same effect on execution order. An explanation would make a better answer.Paamand
@Quentin I tested your suggestion (which works) and posted the full conclusion. However I keep the question open in hope of a better explanation.Paamand

1 Answers

0
votes

Following the comment by Quentin, a solution is to include the argument parenthesis in the EXPAND macro:

#define EXPAND(...) __VA_ARGS__
#define UNPACK(a, b) a, b
#define JOINAB(a, b, S) a S b
#define JOINTUPLE(S, ab) EXPAND(JOINAB EXPAND((UNPACK ab, S)))

The full solution with variadic arguments follows:

#define JOIN1(S, aA) JOINTUPLE(S, aA)
#define JOIN2(S, aA, bB) JOINTUPLE(S, aA), JOINTUPLE(S, bB)
#define JOIN3(S, aA, bB, cC) JOINTUPLE(S, aA), JOINTUPLE(S, bB), JOINTUPLE(S, cC)
#define JOINN(_1, _2, _3, N, ...) JOIN##N
#define JOIN(S,...) _EXPAND(JOINN(__VA_ARGS__,3,2,1)(S,__VA_ARGS__))

which enables the following syntax:

JOIN(:, (a, A)) // Expands to "a : A"
JOIN(., (a, A), (b, B)) // Expands to "a . A, b . B"

An answer to why it is needed to manipulate the syntax with this EXPAND, if it is compiler specific/portable and weather it is good/bad practice would be appreciated.