宏中的波浪号(~)是什么意思?

38

这个网站上看到,代码使用圆括号中的波浪线表示宏调用:

HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (~))
//                                          ^^^

这是什么意思/作用?我怀疑它只是一个空参数,但我不确定。它是否可能是C(99)特有的,就像__VA_ARGS__是C99特有的且存在于C++中一样?


1
~ 是位求补运算符,你可能已经知道了。对我来说,它看起来只是一个占位符。我认为它没有任何特殊的意义。 - Assaf Lavie
3个回答

32
Boost.Preprocessor 的介绍页面中,在A.4.1.1 水平重复一节给出了一个示例。
#define TINY_print(z, n, data) data

#define TINY_size(z, n, unused)                                 \
  template <BOOST_PP_ENUM_PARAMS(n, class T)>                   \
  struct tiny_size<                                             \
      BOOST_PP_ENUM_PARAMS(n,T)                                 \
      BOOST_PP_COMMA_IF(n)                                      \
      BOOST_PP_ENUM(                                            \
          BOOST_PP_SUB(TINY_MAX_SIZE,n), TINY_print, none)      \
  >                                                             \
    : mpl::int_<n> {};

BOOST_PP_REPEAT(TINY_MAX_SIZE, TINY_size, ~) // Oh! a tilde!

#undef TINY_size
#undef TINY_print

下面有解释:
BOOST_PP_REPEAT 是一个高阶宏,通过调用第二个参数(TINY_size)指定的宏来重复调用代码生成过程。第一个参数指定重复调用的次数,第三个参数可以是任何数据; 它将不变地传递给被调用的宏。在此情况下,TINY_size 不使用该数据,因此选择传递 ~ 是任意的。 [5](我强调)
注意:
~ 不是完全随意的选择。 @ 和 $ 都可能是很好的选择,但它们技术上不属于 C++ 实现必须支持的基本字符集。像 ignored 这样的标识符可能会受到宏扩展的影响,导致意外结果。
因此,波浪线只是一个占位符,因为需要一个参数,但不需要任何参数。由于任何用户定义的标识符都可能被扩展,所以您需要使用其他东西。
事实证明,与 + 或 - 等相比,~ 几乎不被使用(二进制否定并不经常被调用),因此几乎没有混淆的机会。一旦您确定了这一点,并始终使用它,就可以将其赋予波浪符的新含义;例如,使用 operator<< 和 operator>> 来流式传输数据已经成为 C++ 习惯用语。

我的加一,这是一个非常好的发现!我找不到任何标准参考,并且一直在努力彻底理解它。你的答案很好地解决了这个问题。 - Alok Save
宏展开如何会导致意外结果,考虑到参数(因此,宏展开的参数)在TINY_size的扩展中并未出现? - Random832
1
@Random832:宏展开一直对我来说有点模糊,但是以#define unused a, b为例,现在TINY_size将被调用4个参数,而不是3个,因此代码将被拒绝。 - Matthieu M.

4

~ 没有任何作用。 几乎任何其他括号内的内容都可以起到同样的作用。

这个技巧的关键是测试是否在 _TRIGGER_PARENTHESIS_ __VA_ARGS__ (~) 的展开中,_TRIGGER_PARENTHESIS_ 紧挨着 (~)。无论哪种情况,HAS_COMMA(...) 都会将其参数展开为 01


我在想,如果参数本身是一个宏的话,会是怎样的情况呢? _TRIGGER_PARENTHESIS_ MYMACRO(〜) - Xeo
他为什么使用(~)而不是(+)或其他什么符号? - Johannes Schaub - litb
@Johannes:在Boost.Preprocessor介绍页面上提供了一个推理,我在下面引用了。目标是使用有效但罕见的预处理器标记。 - Matthieu M.

3
宏的名称和括号之间放置要测试的参数,仅当参数为空时,宏才会触发:
_TRIGGER_PARENTHESIS_ __VA_ARGS__ (~)

注意:实际上你发布的链接已经说明了这一点。我会查找标准中对此的参考。

关于你的笔记:是的,在发布问题后,我阅读了几遍后就想到了...这不是我第一次提问,然后几分钟后就想到解决方案了... :( 此外,你的第一句话似乎并没有直接与问题相关。 - Xeo
@Xeo:抱歉,我恐怕没有完全理解上下文以便发布详细的答案,因为只有~而且我一直在长时间地破坏标准却没有成功。不过,@Matthieu M. 的发现很棒,我可以安心睡觉了! - Alok Save
无论如何,感谢您的努力。 :) - Xeo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接