4

For a single variable (or a given number of variables), it's easy to use macros to stringify variables. E.g. for 2 variables, I can do:

#define STRINGIFY(var1, var2) (std::string(#var1) + " " + #var2)

Is there a way to extend the above with either variadic macros or some other sort of compile-time trickery, to ultimately get a STRINGIFY function that accepts an arbitrary number of arguments?

eddi
  • 49,088
  • 6
  • 104
  • 155

6 Answers6

1

You can't recurse in CPP, but you can #define a lot of macros (DO1, DO2 .. DO128) and then use one "generic" Macro that expands to the macro with the appropriate suffix.

P99 is one lib (a header file actually) that provides the boilerplate to do this. P99_SER pastes arguments delimited with spaces after calling P99_STRINGIFY on each of them.

#include "p99_for.h"
P99_SER(P99_STRINGIFY, first,second,third,fourth,fifth,6)

expands to

$ gcc -E test.c | tail -n 1
    "first" "second" "third" "fourth" "fifth" "6"
a3f
  • 8,517
  • 1
  • 41
  • 46
1

You may use the following (up to hard coded 6 arguments):

#define NARGS_(_1, _2, _3, _4, _5 , _6, N, ...) N
#define NARGS(args...) NARGS_(args..., 6, 5, 4, 3, 2, 1)

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

#define name_1(x)                     #x
#define name_2(x, x2)                 #x , #x2
#define name_3(x, x2, x3)             #x , #x2, #x3
#define name_4(x, x2, x3, x4)         #x , #x2, #x3, #x4
#define name_5(x, x2, x3, x4, x5)     #x , #x2, #x3, #x4, #x5
#define name_6(x, x2, x3, x4, x5, x6) #x , #x2, #x3, #x4, #x5, #x6

#define names(args...) CAT(name_, NARGS(args))(args)

So names(var1, var2) results in "var1", "var2". (That you can pass to any function).
You can also customize name_x.

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • thanks, but I'm looking for having an *arbitrary* number of arguments, not capped by some upper limit of my laziness/code mess I'm willing to tolerate – eddi Mar 04 '16 at 18:39
  • that said, if you have a way of doing what you did by having `O(log(N))` lines of code for a cap of `N` variables - I'd find that acceptable – eddi Mar 04 '16 at 19:20
1

Stringify [an] arbitrary number of variables?

is one question and:

Way to ... get a STRINGIFY function that accepts an arbitrary number of arguments?

is another question. Guessing that the first question is the one that matters to you, this is enough:

#define STRINGIFY(tokz) (std::string(#tokz))

Some nonsense usage:

main.cpp

#include <iostream>

#define STRINGIFY(tokz) (std::string(#tokz))

#define FOO(x,y) \
    {   int x = 1 ; int y = 2 ; std::string s = STRINGIFY(x y); \
        cout << '[' << s  << ']' << " -> " << x << ' ' << y  << '\n'; \
    }
#define BAR(x,y,z) \
    {   char x = 'x'; char y = 'y'; char z = 'z'; \
        std::string s = STRINGIFY([ x y z ]); \
        cout << s << " -> " << x << ' ' << y << ' ' << z << '\n'; \
    } 

using namespace std;
int main()
{
    cout << STRINGIFY(p q) << '\n' << STRINGIFY(r s t) << '\n';
    FOO(a,b);
    BAR(c,d,e);
    return 0;
}

Preprocessed prettily:

$  g++ -E main.cpp | astyle 
...
...
using namespace std;
int main()
{
    cout << (std::string("p q")) << '\n' << (std::string("r s t")) << '\n';
    {
        int a = 1 ;
        int b = 2 ;
        std::string s = (std::string("a b"));
        cout << '[' << s << ']' << " -> " << a << ' ' << b << '\n';
    };
    {
        char c = 'x';
        char d = 'y';
        char e = 'z';
        std::string s = (std::string("[ c d e ]"));
        cout << s << " -> " << c << ' ' << d << ' ' << e << '\n';
    };
    return 0;
}

Run:

$ g++ main.cpp && ./a.out
p q
r s t
[a b] -> 1 2
[ c d e ] -> x y z
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • won't compile as is, one must use STRINGIFY(p q).c_str() since the operator << has no built-in overload with a std::string param – hymced Dec 01 '17 at 22:40
  • @hymced Please see [the documentation](http://en.cppreference.com/w/cpp/string/basic_string/operator_ltltgtgt) and [this demo](http://coliru.stacked-crooked.com/a/3e09349bbdaa05c4) and [that demo](https://godbolt.org/g/hcJYMK) – Mike Kinghan Dec 02 '17 at 09:18
  • very interesting, I wasn't aware of those online compilers! so thanks ;) It appears that in VC++ I must explicitly #include , whereas gcc won't need it... – hymced Dec 02 '17 at 10:57
1

I'm not sure that I understood what you're trying to do. The code below tokenizes, at compile time, __VA_ARGS__. It does not check the syntax: it blindly replaces the white-space and commas with '\0', stores the start of identifiers in arg and the number of arguments in argc.

#include <iostream>

template < unsigned N > constexpr
unsigned countarg( const char( &s )[N], unsigned i = 0, unsigned c = 0 )
{
  return
    s[i] == '\0'
    ? i == 0
    ? 0
    : c + 1
    : s[i] == ','
    ? countarg( s, i + 1, c + 1 )
    : countarg( s, i + 1, c );
}

template < unsigned N > constexpr
unsigned skipid( char( &s )[N], unsigned i = 0 )
{
  return s[i] == '\0' || s[i] == ' ' || s[i] == '\t' || s[i] == ','
    ? i
    : skipid( s, i + 1 );
}

template < unsigned N, unsigned M > constexpr
unsigned tokenize( char( &s )[N], const char*(&a)[M], unsigned i = 0, unsigned j = 0 )
{
  return s[i] == '\0'
    ? i
    : s[i] == ' ' || s[i] == '\t' || s[i] == ','
    ? ((s[i] = '\0'),
      tokenize( s, a, ++i, j ))
    : ((a[j] = s + i),
      i = skipid( s, i ),
      tokenize( s, a, i, ++j ));
}

#define TOKENIZEVA( ... ) char orig[] = #__VA_ARGS__; const unsigned argc = countarg(#__VA_ARGS__); const char* arg[argc]; tokenize( orig, arg );

#define PRINT( ... ) { TOKENIZEVA(__VA_ARGS__) for ( auto s : arg ) std::cout << s << std::endl; }

int main()
{
  PRINT( first, second, third, fourth );
  return 0;
}
zdf
  • 4,382
  • 3
  • 18
  • 29
0

Alas, you can't do recursion in the preprocessor, but you can kinda-sorta make a "apply macro to all arguments to this variadic macro" macro.

A useful idea is that when calling a function-style macro, the () can be part of the argument. This allows you to do some funky things. In your specific example...

First, a macro that tells us how many arguments are in a __VA_ARGS__ pack:

#define NUM_ARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N
#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

Next, a macro that applies a given macro to every element of a __VA_ARGS__ pack independently:

#define FOREACH(MACRO, ...) FOREACH_(NUM_ARGS(__VA_ARGS__), MACRO, __VA_ARGS__)
#define FOREACH_(N, M, ...) FOREACH__(N, M, __VA_ARGS__)
#define FOREACH__(N, M, ...) FOREACH_##N(M, __VA_ARGS__)
#define FOREACH_1(M, A) M(A)
#define FOREACH_2(M, A, ...) M(A)  FOREACH_1(M, __VA_ARGS__)
#define FOREACH_3(M, A, ...) M(A) FOREACH_2(M, __VA_ARGS__)
#define FOREACH_4(M, A, ...) M(A) FOREACH_3(M, __VA_ARGS__)
#define FOREACH_5(M, A, ...) M(A) FOREACH_4(M, __VA_ARGS__)
#define FOREACH_6(M, A, ...) M(A) FOREACH_5(M, __VA_ARGS__)
#define FOREACH_7(M, A, ...) M(A) FOREACH_6(M, __VA_ARGS__)
#define FOREACH_8(M, A, ...) M(A) FOREACH_7(M, __VA_ARGS__)
// Extend in the obvious way for as many iterations as needed.

A simple "stringify one thing" macro (that makes sure its argument expands):

#define STRINGIFY_(X) #X
#define STRINGIFY(X) STRINGIFY_(X)

And now let's put all the pieces together to make a nice example that builds a string array:

#define STRINGIFY_ALL(...) FOREACH(STRINGIFY, __VA_ARGS__)

#define COMMA(X) X,
#define COMMA_STRINGIFY(X) COMMA(STRINGIFY(X))

#define STRING_LITERAL_ARRAY(...) const char* STUFF[ NUM_ARGS(__VA_ARGS__) ] = { FOREACH(COMMA_STRINGIFY, __VA_ARGS__) };

STRING_LITERAL_ARRAY(I, AM, A, POTATO);

// Will yield:
const char* STUFF[ 4 ] { "I", "AM", "A", "POTATO" };

Hopefully you can see how you can write different "lambda-like" macros to use with FOREACH to do all sorts of exciting things.

Chris Kitching
  • 2,559
  • 23
  • 37
-2

Try __VA_ARGS__ macro.

#include <stdio.h>

#define STRINGIFY(...) #__VA_ARGS__

int main()
{
    int var1;
    int var2;
    int varN;

    printf(STRINGIFY(var1 var2 varN)); // or STRINGIFY(var1, var2, varN)

    return 0;
}

Output:

var1 var2 varN
DVG
  • 64
  • 5
  • That's not the same as concatenating the plain arguments to get a symbol. – πάντα ῥεῖ Mar 04 '16 at 18:30
  • You did not separate the arguments with commas. And even if you did, `__VA_ARGS__` does not process comma, therefore what you will get is a string made of arguments separated by commas. `__VA_ARGS__` is almost an exact copy of the tail arguments. – zdf Mar 04 '16 at 22:13