字符串化任意数量的变量

stringify arbitrary number of variables

对于单个变量(或给定数量的变量),很容易使用宏将变量字符串化。例如。对于 2 个变量,我可以这样做:

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

有没有办法用可变参数宏或其他类型的编译时技巧来扩展上述内容,最终得到一个接受任意数量参数的 STRINGIFY 函数?

你不能在 CPP 中递归,但你可以 #define 很多宏(DO1DO2 .. DO128),然后使用一个 "generic" 扩展为具有适当后缀的宏的宏。

P99 is one lib (a header file actually) that provides the boilerplate to do this. P99_SER 在对每个参数调用 P99_STRINGIFY 后粘贴以空格分隔的参数。

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

扩展到

$ gcc -E test.c | tail -n 1
    "first" "second" "third" "fourth" "fifth" "6"

试试 __VA_ARGS__ 宏。

#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;
}

输出:

var1 var2 varN

您可以使用以下(最多硬编码 6 个参数):

#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)

所以 names(var1, var2) 结果是 "var1", "var2"。 (你可以传递给任何函数)。
您还可以自定义 name_x.

Demo

唉,你不能在预处理器中做递归,但你可以有点做一个 "apply macro to all arguments to this variadic macro" 宏。

一个有用的想法是,在调用函数式宏时,() 可以是 参数 的一部分。这允许你做一些时髦的事情。在您的具体示例中...

首先,一个宏告诉我们 __VA_ARGS__ 包中有多少个参数:

#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)

接下来,一个将给定宏独立应用于 __VA_ARGS__ 包的每个元素的宏:

#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.

一个简单的 "stringify one thing" 宏(确保其参数展开):

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

现在让我们把所有的部分放在一起来制作一个构建字符串数组的好例子:

#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" };

希望您能了解如何编写不同的 "lambda-like" 宏以与 FOREACH 一起使用来完成各种令人兴奋的事情。

字符串化[一个]任意数量的变量?

是一题并且:

如何...获得接受任意数量参数的 STRINGIFY 函数?

又是一个问题。猜测 第一个 问题是最重要的问题 对你来说,这就足够了:

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

一些废话用法:

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;
}

漂亮地预处理:

$  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;
}

运行:

$ g++ main.cpp && ./a.out
p q
r s t
[a b] -> 1 2
[ c d e ] -> x y z

我不确定我是否理解您的意图。下面的代码在编译时标记化 __VA_ARGS__。它不检查语法:它盲目地将白色 space 和逗号替换为 '[=12=]',将标识符的开头存储在 arg 中,将参数数量存储在 argc 中。

#include <iostream>

template < unsigned N > constexpr
unsigned countarg( const char( &s )[N], unsigned i = 0, unsigned c = 0 )
{
  return
    s[i] == '[=10=]'
    ? 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] == '[=10=]' || 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] == '[=10=]'
    ? i
    : s[i] == ' ' || s[i] == '\t' || s[i] == ','
    ? ((s[i] = '[=10=]'),
      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;
}