2

How to concatenate all of the arguments of a variadic macro into a quoted string ?

Below is a definition of non-variadic macro that concatenates 2 arguments into a quoted string:

#define TO_STRING(x) #x
#define CONCAT_STRINGIFY(x,y) TO_STRING(x##y)

Invoking this macro like this:

CONCAT_STRINGIFY(AAA,BBB)

...produces the following output:

"AAABBB"

How to make the macro CONCAT_STRINGIFY(...) variadic, so that it accepts an arbitrary number of arguments ?

P.S.
The solution can use only the C or C++ Preprocessor and no 3rd party libraries
BTW: I am not passing the preprocessor's output to a C/C++ compiler.

  • This looks similar: https://stackoverflow.com/questions/69968476/is-it-possible-to-concatenate-parameters-of-variadic-macro-to-form-a-variable-na – Barmar Dec 07 '22 at 23:28
  • 1
    Ouch, macro hell! Why do you need this, pray? – Paul Sanders Dec 07 '22 at 23:30
  • @Barmar: What you have linked has a user definable delimiter. In my question there is no such delimiter. That makes it simpler. – Pavel Stepanek Dec 07 '22 at 23:43
  • That's why I said it's similar, not exactly the same. Can't you just use the same technique without the delimiter? – Barmar Dec 07 '22 at 23:44
  • Note that, for c/c++ output, you can simply call `TO_STRING()` for each argument. In c/c++, `"abc" "def"` is valid and is the same as `"abcdef"`. – lorro Dec 07 '22 at 23:48
  • @Barmar: No, I really dislike that solution. It has an upper limit for arguments, manually enumerates non-variadic macros and is cumbersome. I will as soon use separate `CONCAT_STRINGIFY_n` macros for different number of arguments than the technique shown in that other post. I am hoping that someone will find a better solution by taking advantage of the fact that I do not need a "user defined delimiter". – Pavel Stepanek Dec 07 '22 at 23:49
  • The whole reason it does it that way is because this is the only way to do it in the preprocessor. It's not a general purpose language, it doesn't have loops. – Barmar Dec 07 '22 at 23:51
  • @lorro: The solution can use only the C/C++ Preprocessor. It is not for preprocessing C code. I stated specifically that it must use ONLY the Preprocesor. It cannot use the C compiler. Without the compiler the adjacent quoted strings WILL NOT be concatenated. – Pavel Stepanek Dec 07 '22 at 23:51
  • This is a working hack: `#define CONCAT_STRINGIFY(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z) TO_STRING(A##B##C##D##E##F##G##H##I##J##K##L##M##N##O##P##Q##R##S##T##U##V##W##X##Y##Z)` but it requires the traditional preprocessor `/Zc:preprocessor-` and generates the C4003 warning. I am unable to disable this warning in MSVC with `#pragma warning( disable : 4003 )` and even if I could, all `#pragma ...` directives remain in the output after preprocessing, which is unacceptable because I am not passing the preprocessor's output to a C compiler. – Pavel Stepanek Dec 08 '22 at 14:25

2 Answers2

3

Here is a general solution. It works with up to 342 arguments, but can be exponentially expanded to work with more arguments by adding more EVALs.

#define EVAL1(...) __VA_ARGS__
#define EVAL2(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
#define EVAL5(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))

#define EMPTY()

#define TUPLE_AT_1(a,b,...) b
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__)
#define CAT_PROBE(...) ,CAT_END,

#define CAT_IND() CAT_
#define CAT_(x,a,...) CHECK(CAT_PROBE a,CAT_NEXT)(x,a,__VA_ARGS__)
#define CAT_NEXT(x,a,...) CAT_IND EMPTY()()(x##a,__VA_ARGS__)
#define CAT_END(x,a,...) #x

#define CAT(...) EVAL5(CAT_(,__VA_ARGS__,()))
CAT(a,b,c,d,e,f,g,h,i,j,k) // "abcdefghijk"

It uses macro recursion to concatenate all arguments, until an artificially appended "()" argument is reached. I've chosen "()" because it isn't pastable, and easy to detect.

Since MSVC by default doesn't implement a standard conforming preprocessor, you'll need to enable the /Zc:preprocessor flag for the above code to work on MSVC.

Edit:

If you don't care about a general solution, here is a nice and compact one with up to 16 arguments:

#define CAT_(a,b,c,d,e,f,g,i,j,k,l,m,n,o,p,...) a##b##c##d##e##f##g##i##j##k##l##m##n##o##p
#define CAT(...) CAT_(__VA_ARGS__,,,,,,,,,,,,,,,,,)
#define STR(...) #__VA_ARGS__
#define STRe(...) STR(__VA_ARGS__)
STRe(CAT(1,2)) // "12"
STRe(CAT(1,2,3,4,5,6,7)) // "1234567"
camel-cdr
  • 643
  • 4
  • 8
  • Please add a disclaimer to your answer that your solutions do not work with the traditional MSVC preprocessor ( option: `/Zc:preprocessor-` ) and I will accept your answer. See: https://godbolt.org/z/5qexvb1Wj – Pavel Stepanek Dec 08 '22 at 14:37
  • If you know of a more general solution that works on all types of preprocessors, please include it as well. – Pavel Stepanek Dec 08 '22 at 14:56
  • 1
    @PavelStepanek will do, but your question didn't specify msvc, and the above solution should be strictly confirming to the C standard. You can't account for all faulty implementations. – camel-cdr Dec 08 '22 at 16:37
  • You are correct and I will have to implement a universal concatenator for MSVC by using `#if !defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL` using your method for the non-traditional preprocessor and something like: `#define CONCAT_STRINGIFY(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z) TO_STRING(A##B##C##D##E##F##G##H##I##J##K##L##M##N##O##P##Q##R##S##T##U##V##W##X##Y##Z)` for the traditional preprocessor. Unfortunately the latter causes the C4003 warning, which cannot be disabled by `#pragma warning( disable : 4003 )` – Pavel Stepanek Dec 08 '22 at 17:08
1

Two solutions.

1.

Based on the usual COUNT pattern, which is described in the answer on the other question (in comments) from Jarod42:

First, you define argument counting macros. You might add arbitrary number of arguments (up to compiler limit) to COUNT_N and numbers in the definition of COUNT. It'll have the numbers in decreasing order after __VA_ARGS__, thus it returns the argument count:

#define COUNT_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...)    N
#define COUNT(...)   COUNT_N(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
// Warning: COUNT() return 1 (as COUNT(A)) :-/

Then you'll need the usual IDENTITY and APPLY definitions:

#define IDENTITY(N) N
#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

Finaly, you add a dispatcher based on arg count. Unfortunately, you have to create a case for each number of arguments; if you like, you might generate that code (yes, even with preprocessor, but in a distinct run):

#define CONCAT_STRINGIFY_DISPATCH(N) CONCAT_STRINGIFY ## N

#define TO_STRING(X) #X
#define CONCAT_STRINGIFY1(A) TO_STRING(A)
#define CONCAT_STRINGIFY2(A, B) TO_STRING(A ## B)
#define CONCAT_STRINGIFY3(A, B, C) TO_STRING(A ## B ## C)
#define CONCAT_STRINGIFY4(A, B, C, D) TO_STRING(A ## B ## C ## D)
// ...

#define CONCAT_STRINGIFY(...) IDENTITY(APPLY(CONCAT_STRINGIFY_DISPATCH, COUNT(__VA_ARGS__)))(__VA_ARGS__)

CONCAT_STRINGIFY(AAA, BBB, CCC)

Perhaps a little more 'hacky', but sorter solution is to concatenate many arguments and pass empty arguments as needed:

#define TO_STRING(X) #X
#define CONCAT_STRINGIFY_4(A, B, C, D, ...) TO_STRING(A ## B ## C ## D)
#define CS(...) CONCAT_STRINGIFY_4(__VA_ARGS__,,,,)

This works for up to 4 args, you might add more as needed.

lorro
  • 10,687
  • 23
  • 36
  • In your second solution `CS(AAA,BBB,CCC)` expands to `"AAA,BBB,CCC"`. The commas should not be a part of the expansion so this does not work. See: https://godbolt.org/z/o1cMq4snM – Pavel Stepanek Dec 08 '22 at 07:50
  • BTW: This is a working hack, too: `#define CONCAT_STRINGIFY(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z) TO_STRING(A##B##C##D##E##F##G##H##I##J##K##L##M##N##O##P##Q##R##S##T##U##V##W##X##Y##Z)` but it generates the C4003 warning. I am unable to disable this warning in MSVC with `#pragma warning( disable : 4003 )` and even if I could, all `#pragma ...` directives remain in the output after preprocessing, which is unacceptable because I am not passing the preprocessor's output to a C compiler. – Pavel Stepanek Dec 08 '22 at 08:55
  • @PavelStepanek That's non-standard MSVC thing I think. – lorro Dec 08 '22 at 10:41
  • 1
    @PavelStepanek try enabling /Zc:preprocessor to get a standard conforming preprocessor on MSVC – camel-cdr Dec 08 '22 at 11:09
  • Yes, my ugly hack works only with the traditional preprocessor `/Zc:preprocessor-` That is why I asked this question on StackOverflow to find a more general solution. – Pavel Stepanek Dec 08 '22 at 14:53
  • @PavelStepanek Thanks for the upvote. Also, just to clarify, camel-cdr's fix works for MSVC (it removes commas). – lorro Dec 08 '22 at 19:06