Removing last trailing comma from the arguments of a macro

Viewed 361

I need to remove the last trailing comma from a list of macro arguments (because they will be eventually expanded into template arguments where the trailing comma is not admitted).

So I need a macro remove_trailing_comma() which called like remove_trailing_comma(arg1, arg2, arg3, ) expands to arg1, arg2, arg3.

I've tried with different combinations of varargs and __VA_OPT__ but I seem to not be able to do it.

For example:

#define discard_trailing_comma(arg, ...) \
    arg __VA_OPT__(,) discard_trailing_comma(__VA_ARGS__)

discard_trailing_comma(1, 2, 3, )

does not work (with g++ 10) because expands to 1 , discard_trailing_comma(2, 3,), I do not know why (are macro not expanded recursively?)

Is this possible in C++20?

2 Answers

You'll probably should go with @eljay's answer, but if you need to support way more arguments, here is one that supports ~2000 arguments in 22 lines and adding more lines grows that number exponentially.

#define E4(...) E3(E3(E3(E3(E3(E3(E3(E3(E3(E3(__VA_ARGS__))))))))))
#define E3(...) E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(__VA_ARGS__))))))))))
#define E2(...) E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(__VA_ARGS__))))))))))
#define E1(...) __VA_ARGS__

#define EMPTY()
#define TUPLE_AT_2(x,y,...) y
#define TUPLE_TAIL(x,...) __VA_ARGS__

#define CHECK(...) TUPLE_AT_2(__VA_ARGS__,0,)
#define EQ_END_END ,1

#define SCAN(...) __VA_ARGS__
#define CAT(a,b) CAT_(a,b)
#define CAT_(a,b) a##b

#define LOOP_() LOOP
#define LOOP(x,y,...) CAT(LOOP, CHECK(EQ_END_##y))(x,y,__VA_ARGS__)
#define LOOP1(x,...) (TUPLE_TAIL x)
#define LOOP0(x,y,...) LOOP_ EMPTY() ()((SCAN x, y),__VA_ARGS__)

#define DTC(...) E4(LOOP((),    __VA_ARGS__ END))

DTC (1, 2, 3, 4, 5, 6, 7, 8, 9,) // expands to: (1, 2, 3, 4, 5, 6, 7, 8, 9)

Let me try to explain this.

Firstly, when LOOP is called inside E4() it can recursively call itself, which is done in LOOP0. The simplest example for this would be #define LOOP(...) __VA_ARGS__ LOOP_ EMPTY() ()(__VA_ARGS__) which repeats the argument until the recursion limit, which is bound by the nesting of E4. Understanding DEFER and OBSTRUCT macros already explains the behavior quite well, so I'll skip that part of the explanation.

Now the idea is the following: We loop over every argument until we reach the last one, where we've inserted an END mark. Whilst doing that, we construct a new argument list, but this also stops when we reach the END mark.

CAT(LOOP, CHECK(EQ_END_##y)) branches to LOOP1 if the argument y holds the end mark END and otherwise to LOOP0.

LOOP1 appends a new argument to our argument list in x using (SCAN x, y). Since we start off with an empty argument list, we'll end up with a leading empty argument, which we can trivially remove on LOOP0.

PS: This concept can be trivially extended to E5, E6, ..., although there is a larger overhead using that because once the recursion ends the preprocessor still needs to rescan the result till the recursion limit. If you want to remedy that, you can use something like the continuation machine from order-pp, which can actually terminate, but it's about 150 loc.

Edit, I just revisited this and realized that using x to build up the tuple is quite inefficient, here is a version that doesn't do that:

#define E4(...) E3(E3(E3(E3(E3(E3(E3(E3(E3(E3(__VA_ARGS__))))))))))
#define E3(...) E2(E2(E2(E2(E2(E2(E2(E2(E2(E2(__VA_ARGS__))))))))))
#define E2(...) E1(E1(E1(E1(E1(E1(E1(E1(E1(E1(__VA_ARGS__))))))))))
#define E1(...) __VA_ARGS__

#define EMPTY()
#define FX(f,x) f(x)
#define TUPLE_AT_2(x,y,...) y
#define TUPLE_TAIL(x,...) __VA_ARGS__

#define CHECK(...) TUPLE_AT_2(__VA_ARGS__,0,)
#define EQ_END_END ,1

#define CAT(a,b) CAT_(a,b)
#define CAT_(a,b) a##b

#define LOOP_() LOOP
#define LOOP(x,...) CAT(LOOP, CHECK(EQ_END_##x))(x,__VA_ARGS__)
#define LOOP1(x,...) 
#define LOOP0(x,...) LOOP_ EMPTY() ()(__VA_ARGS__),x

#define DTC(...) (FX(TUPLE_TAIL,E4(LOOP(__VA_ARGS__ END))))

DTC (1, 2, 3, 4, 5, 6, 7, 8, 9,) // expands to: (1, 2, 3, 4, 5, 6, 7, 8, 9)

Assuming you have a bounded number of parameters, you could make a delegate function-like macro as a helper to choose the appropriate implementation macro.

#define GET_DTC(_1,_2,_3,_4,NAME,...) NAME
#define DTC4(_1,_2,_3,_4) _1,_2,_3
#define DTC3(_1,_2,_3) _1,_2
#define DTC2(_1,_2) _1
#define DTC1(_1)
#define discard_trailing_comma(...) \
    GET_DTC(__VA_ARGS__, DTC4, DTC3, DTC2, DTC1)(__VA_ARGS__)

int arr[] = {
discard_trailing_comma(1, 2, 3, )
};
Related