【问题标题】:stringify arbitrary number of variables字符串化任意数量的变量
【发布时间】:2016-03-04 17:51:42
【问题描述】:

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

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

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

【问题讨论】:

  • 我认为答案已经给出here。直接这样做看起来很乏味。
  • 为什么要使用宏?
  • @πάνταῥεῖ 希望我能理解这个问题发生了什么
  • @NickyC 我对非宏观解决方案非常满意,我只是不知道
  • @NickyC Stringifying 是您真正需要 c 预处理器的罕见功能之一。

标签: c++ c-preprocessor


【解决方案1】:

我不确定我是否理解您要执行的操作。下面的代码在编译时标记了__VA_ARGS__。它不检查语法:它盲目地将空格和逗号替换为'\0',将标识符的开头存储在arg 中,并将参数数量存储在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;
}

【讨论】:

    【解决方案2】:

    您可以使用以下(最多硬编码 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

    【讨论】:

    • 谢谢,但我正在寻找任意个参数,不受我愿意容忍的懒惰/代码混乱的上限跨度>
    • 也就是说,如果您有办法通过使用 O(log(N)) 代码行来设置 N 变量的上限 - 我会发现这是可以接受的
    【解决方案3】:

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

    P99 是一个库(实际上是一个头文件),它提供了执行此操作的样板。 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"
    

    【讨论】:

      【解决方案4】:

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

      是一个问题并且:

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

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

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

      一些无意义的用法:

      ma​​in.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
      

      【讨论】:

      • 不会按原样编译,必须使用 STRINGIFY(p q).c_str() 因为运算符
      • 很有趣,我不知道那些在线编译器!非常感谢 ;) 看来在 VC++ 中我必须明确地#include ,而 gcc 不需要它...
      【解决方案5】:

      唉,你不能在预处理器中进行递归,但你可以制作一个“将宏应用于这个可变参数宏的所有参数”宏。

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

      首先,一个宏告诉我们__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”的宏来与 FOREACH 一起使用来完成各种令人兴奋的事情。

      【讨论】:

      • 这几乎是完美的,但在 0 参数的情况下不起作用。你最终会得到“”,而不是什么都没有。
      【解决方案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
      

      【讨论】:

      • 这与连接普通参数以获得符号不同。
      • 您没有用逗号分隔参数。即使你这样做了,__VA_ARGS__ 也不会处理逗号,因此你会得到一个由逗号分隔的参数组成的字符串。 __VA_ARGS__ 几乎是尾部参数的精确副本。
      猜你喜欢
      • 2017-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-19
      • 2023-03-21
      • 2021-02-04
      相关资源
      最近更新 更多