【问题标题】:Writing preprocessor directives to get string编写预处理器指令以获取字符串
【发布时间】:2010-03-11 05:47:20
【问题描述】:

你可以编写预处理指令来返回一个 std::string 或 char* 吗?

例如:如果是整数:

#define square(x) (x*x)

int main()
{
   int x = square(5);
}

我希望做同样的事情,但使用类似 switch-case 模式的字符串。如果通过 1 它应该返回 "One" 和 2 对于 "Two" 等等..

【问题讨论】:

  • 为什么不只是一个普通的功能?
  • Martin 所说的 - 以及您的 square() 宏示例说明了一个原因:很容易出错。 square() 宏应该更像 #define square(x) ((x)*(x)) 以避免出现 square(1 + 4) 返回 9 而不是 25 之类的问题。即使使用该修复程序,也很难防止带有副作用的参数的错误行为。一个函数可以避免这些问题,并且可能不会对性能产生明显影响(特别是如果它可以设为inline)。
  • 通过使用简单的宏或内联函数,是只在编译时或运行时才能达到性能吗?

标签: c++ c-preprocessor


【解决方案1】:

您不想在 C++ 中使用宏来执行此操作;一个功能很好:

char const* num_name(int n, char const* default_=0) {
  // you could change the default_ to something else if desired

  static char const* names[] = {"Zero", "One", "Two", "..."};
  if (0 <= n && n < (sizeof names / sizeof *names)) {
    return names[n];
  }
  return default_;
}

int main() {
  cout << num_name(42, "Many") << '\n';
  char const* name = num_name(35);
  if (!name) { // using the null pointer default_ value as I have above
    // name not defined, handle however you like
  }
  return 0;
}

同样,square应该是一个函数:

inline int square(int n) {
  return n * n;
}

(虽然在实践中 square 不是很有用,但您只需直接相乘即可。)


出于好奇,虽然在这种情况下我不推荐它(上面的函数很好),但模板元编程等效项是:

template<unsigned N> // could also be int if desired
struct NumName {
  static char const* name(char const* default_=0) { return default_; }
};
#define G(NUM,NAME) \
template<> struct NumName<NUM> { \
  static char const* name(char const* default_=0) { return NAME; } \
};
G(0,"Zero")
G(1,"One")
G(2,"Two")
G(3,"Three")
// ...
#undef G

请注意,TMP 示例失败的主要原因是您必须使用编译时常量而不是任何 int。

【讨论】:

  • 这让我想知道这个问题是否可以通过模板元编程以某种巧妙的方式实现......
  • @Chris:当然,您必须使用NumName&lt;compile_time_constant&gt;::name()(调用内联静态函数)并专门处理您关心的所有值。但是,我认为没有必要。
  • @Roger - 我想知道更多是否可以让我们做巧妙的技巧将较大的数字简化为较小数字的组合,但模板系统不允许范围(比如,@ 987654325@) 所以它最终不是很有用。
  • @Chris:模板系统当然允许这样做,尽管不是直接的。你通常会通过从NumNameImpl&lt;I, HorribleExpression&lt;I&gt;::value&gt; 继承NumName&lt;I&gt; 来做到这一点。您现在可以专攻NumName&lt;int&gt;HorribleExpression&lt;int&gt; 和/或NumNameImpl&lt;int, int&gt;
【解决方案2】:

#define 预处理器指令确实替换了源代码中的字符串。您想要的 case...when 构造仍然不是微不足道的:

#define x(i) ((i)==1?"One":((i)==2?"Two":"Many"))

可能是一个开始 -- 但定义类似

static char* xsof[] = ["One", "Two", "Many"];

#define x(i) xsof[max(0, min((i)-1, (sizeof xsof / sizeof xsof[0] - 1)))]

似乎更合理,性能更好。

编辑:根据 Chris Lutz 的建议,使第二个宏自动调整为 xsof 定义;根据 Mark 的说法,计数从 1 开始。

【讨论】:

  • 将宏中的2 更改为(sizeof xsof / sizeof xsof[0] - 1),您就有了一个出色的、可维护的解决方案。
  • 给定宏中的(i)不应该真的是(i) - 1吗?
  • @Michael,啊,是的,他说过他想从一开始数——编辑到修复,tx。 @Chris,好主意,正在编辑以合并它。
  • @Michael - 在这种情况下,最好使用max(1, min((i), sizeof xsof / sizeof xsof[0])) - 1(或者只有我一个人?)或将"Zero" 合并到xsof
【解决方案3】:

我看过这个...

#define STRING_1() "ONE"
#define STRING_2() "TWO"
#define STRING_3() "THREE"
...

#define STRING_A_NUMBER_I(n) STRING_##n()

#define STRING_A_NUMBER(n) STRING_A_NUMBER_I(n)  

我相信这个额外的步骤是为了确保 n 被评估,所以如果你传递 1+2,它会在传递给 STRING_A_NUMBER_I 之前转换为 3,这似乎有点躲闪,谁能详细说明?

【讨论】:

  • Boost 使用这种方法进行预处理器代码生成。破译所有这些是如何工作的很有趣。另外,@Chris Lutz,如果 "STRING_##n" 上面有开/关括号,它将起作用
  • @Dave:对不起,我也不知道你的意思,你必须确保你传递的是一个实际的数字,例如,你不能传递 1+2,你必须通过 3
  • 嗯,我认为这是 Roger Pate 用两个参数定义它来给每个参数一个值而不是默认值的原因。
【解决方案4】:

您不能将整数转换为字符串,因此 1 ---> "One"、2 ---> "Two" 等,除非枚举每个值。

您可以使用 C 预处理器将参数值转换为字符串:

#define STRINGIZER(x)   #x
#define EVALUATOR(x)    STRINGIZER(x)
#define NAME(x)         EVALUATOR(x)

NAME(123)    // "123"

#define N   123
#define M   234

NAME(N+M)    // "123+234"

另见SO 1489932

【讨论】:

    猜你喜欢
    • 2011-01-11
    • 2011-10-23
    • 1970-01-01
    • 2016-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多