【问题标题】:Macros to return defined(X) as true or false将定义的(X)返回为真或假的宏
【发布时间】:2014-10-09 19:41:25
【问题描述】:

我想要一个宏序列来替换下面的代码

#ifdef FOO
    return true;
#else
    return false;
#endif

类似的东西

return MAGICLY_EXPANDING_IFDEFINED_MACRO(FOO);

你可以猜到,有很多 FOO,足以将 4 行缩减为 1 行就很酷了。但实际上它会用一行替换一个怪物 switch 语句。

【问题讨论】:

  • FOO 是否在某处未定义(并重新定义)?
  • FOO 未定义,或者已使用简单的#define FOO 设置。 (这是一个编译选项。)
  • 该问题同时标记为 c 和 c++。您使用哪种语言?
  • C++,虽然对于预处理器,我认为这个文件没有任何特别的 C++ 特性。所以我认为 C 大师会有同样好的答案。
  • 无法在#ifdef/#if defined 中使用不存在的符号

标签: c++ c macros c-preprocessor


【解决方案1】:

在 C++ 中,defined 的行为仅用于条件包含(#if#elif)。所以你不能以任何其他方式使用它。

(相关规则见标准第16.1节)

但是,如果你想检测专门针对空字符串的#define 的宏,则不需要defined(),你可以这样做:

#include <iostream>

/* this matches */
#define FOO

/* this will not */
#undef BAZ

/* nor will these */
#define BAR 0
//#define BAR 1

#define STRINGIZE(x) (#x)
#define EXPAND_IF_DEFINED(y) (!*STRINGIZE(y))

int main()
{
    std::cout << +EXPAND_IF_DEFINED(FOO) << '\n'
              << +EXPAND_IF_DEFINED(BAR) << '\n'
              << +EXPAND_IF_DEFINED(BAZ) << '\n';
}

警告,-D 选项不会是空字符串。要处理这种情况,您可以使用类似

#define IS_MACRO_DEFINED_OR_ONE(y) (!*STRINGIZE(y) || '1'==*STRINGIZE(y))

一个更强大的测试将是

#define IS_MACRO_DEFINED_NOT_TO_ITSELF(y) strcmp(#y, STRINGIZE(y))

由于文字是编译时常量,编译器可能会将编译期间的 strcmp 调用优化为常量 truefalse。你也可以制作一个constexpr 版本的strcmp 来使用。

【讨论】:

  • 你可能会编写一个脚本,给定一个宏名称列表,将为每个宏生成类似于上面的 C 代码。
  • 这基本上是现有开关的目的,每个开关都有一个#if / #else。
  • strcmp 版本很棒。它甚至适用于#define FOO "FOO",并且 GCC 已经在默认优化级别消除了函数调用。当然,如果我们需要编译时常量,我们可以轻松编写自己的constexpr strcmp(const char * a, const char * b)
  • 要是我能投两次票就好了……在#define FOO FOO这样的一些极端情况下仍然失败,但这比我的要好
  • @PiotrS.:我选择了IS_MACRO_DEFINED_NOT_TO_ITSELF 这个名字是为了让它成为一个“令人惊讶的功能”,而不是一个错误。
【解决方案2】:

对于像#define FOO#define FOO 1 这样的简单定义,下面的宏可以很好地工作,但@BenVoigt 的答案中提供了更通用的解决方案。

#define MAGICLY_EXPANDING_IFDEFINED_MACRO_I(X) (#X[0] == 0 || #X[0] == '1')
#define MAGICLY_EXPANDING_IFDEFINED_MACRO(X) MAGICLY_EXPANDING_IFDEFINED_MACRO_I(X)

DEMO


问答部分

它是如何工作的?

那个奇怪的#-something 将define 的值或define 名称本身(如果未定义)字符串化。因此,对于已定义的 FOO(内容为空),编译器最终会出现以下条件:

(""[0] == 0 || ""[0] == '1')

而对于未定义的FOO,条件是:

("FOO"[0] == 0 || "FOO"[0] == '1')

== 0 部分匹配以每个 原始字符串文字 结尾的 \0 字符(即使是空的 ""),而 == '1' 条件存在以防有人定义值 @ 987654334@,比如#define FOO 1

【讨论】:

  • 我很欣赏您的解决方案,但我无法弄清楚它是如何工作的。请解释一下好吗?
  • @5gon12eder:需要两层宏,让处理器有机会替换在字符串化之前定义的名称。 # 是预处理器字符串化运算符,它基本上在命名的宏参数周围抛出双引号。所以在我的回答中,EXPAND_IF_DEFINED(FOO) 变为 !*STRINGIZE() 变为 !*""。但是EXPAND_IF_DEFINED(BAZ) 变成了!*STRINGIZE(BAZ) 变成了!*"BAZ"
  • 谢谢,我现在明白了。但是这种方法有一个明显的限制,即您(@BenVoigt)在您的答案中列出。如果我们#define FOO 42 它给出一个假阴性。这可以通过使用(#X[0] == 0 || (#X[0] &gt;= '0') &amp;&amp; (#X[0] &lt;= '9')) 稍微改进,因此至少,所有#define FOO &lt;NUMBER&gt; 都被正确检测到,因为[0-9] 无论如何都不是有效的宏标识符开始。
  • @5gon12eder 查看问题下的 cmets,我在发布答案之前询问了 define 的可能值
  • 我同意数字测试是最好的,然后我意识到事实并非如此。答案已更新。
【解决方案3】:

宏通常是一条糟糕的路线。你想达到什么目的?这样的事情会起作用:

//Stash this away in a header file
#ifndef FOO
#define RETURN false
#else
#define RETURN true
#endif

return RETURN;

【讨论】:

  • 我有FOOFOO1,...FOO100
  • 但是为什么呢?有模板或变量时为什么要使用宏?
  • 遗留代码,但都是条件编译。基本上,“这个可执行文件是否支持功能 X”?
  • 啊,我明白了。好吧,不幸的是(或者可能是出于理智考虑幸运)您不能在宏中嵌入预处理器指令。
猜你喜欢
  • 2014-09-07
  • 2021-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-05
  • 1970-01-01
  • 2013-06-14
  • 1970-01-01
相关资源
最近更新 更多