C++宏魔法:__VA_OPT__操作

在阅读chromium源码的时候,在\blink render core的base\check.h头文件中,发现了这个定义:

#define CHECK(condition, ...)                                              \
  LOGGING_CHECK_FUNCTION_IMPL(                                             \
      ::logging::CheckError::Check(#condition __VA_OPT__(, ) __VA_ARGS__), \
      condition)

#define PCHECK(condition)                                                \
  LOGGING_CHECK_FUNCTION_IMPL(::logging::CheckError::PCheck(#condition), \
                              condition)

其中 __VA_OPT__ 引起了我的注意。

什么是__VA_OPT__

__VA_OPT__ 被设计用于处理可变参数宏(variadic macros)中的逗号插入或移除问题。
这个宏是在C++20标准中引入的,用于解决在可变参数宏中逗号操作的不确定性,特别是在需要条件性地添加或去除逗号的情况下。

可变参数宏简介

在C++中,可变参数宏允许宏接受不定数量的参数。它们由 _VA_ARGS_ 标识符标识,代表了宏调用中传递的所有额外参数。在C++11中,我们可以这样定义一个可变参数宏:

#define PRINT(...) printf(__VA_ARGS__)

__VA_OPT__ 的作用

__VA_OPT__ 的主要用途是在宏展开时控制逗号的出现。在宏中,逗号 , 是一个特殊的字符,它用于参数列表中的参数分隔符。在某些情况下,我们需要在宏展开时有条件地插入或移除逗号。

例如,假设我们想创建一个宏,该宏可以在参数之间插入一个字符串,但在最后一个参数之后不应该有逗号:

#define JOIN(...) JOIN_IMPL(__VA_ARGS__ __VA_OPT__(,))
#define JOIN_IMPL(...) __VA_ARGS__" joined by "__VA_ARGS__

在这个例子中,__VA_OPT__(,) 将在有多个参数时插入一个逗号,但在只有一个参数时不会。如果宏调用是 JOIN(1, 2),则结果将是 "1 joined by 2";但如果调用是 JOIN(1),则结果将是 "1",没有多余的逗号。

扩展思考,其他相关宏

  • __VA_ARGS__:这是可变参数宏的参数包,代表了传递给宏的任意数量的参数。
  • __VA_LIST__:在C++11中引入,用于在宏内部表示参数包。但在C++17之后,__VA_ARGS__ 成为了首选,因为它的行为更明确。
  • __VA_START____VA_END__:这两个宏在C++20中被提议,但最终没有被标准化。它们的目的是用于标记参数包的开始和结束,以便在宏体内安全地操作参数包。

使用 __VA_OPT__ 的注意事项

  • __VA_OPT__ 必须在宏的参数列表中紧随参数包之后。
  • __VA_OPT__ 后面的括号中必须包含一个逗号或空参数,否则它将不起作用。
  • __VA_OPT__ 的使用需要编译器支持。例如,在MSVC中,__VA_OPT__ 的支持是需要通过编译选项/Zc:preprocessor打开才能使用。因为笔者刚试了一下,发现不添加这个选项会报错,并提供warning指引:
  • warning C5109: 在宏中使用 VA_OPT 需要“/Zc:preprocessor”

简而言之,__VA_OPT__ 提供了一种简洁的方法来处理可变参数宏中的逗号逻辑,使得宏定义更加灵活和健壮。

理解开头的Chromium中的代码片段

开头的宏定义 CHECK 是一个常见的模式,用于在代码中插入条件检查,通常用于调试或断言条件。
现在,让我们逐步分解这个宏定义以更好地理解它:

#define CHECK(condition, ...)                                              \
  LOGGING_CHECK_FUNCTION_IMPL(                                             \
      ::logging::CheckError::Check(#condition __VA_OPT__(, ) __VA_ARGS__), \
      condition)

逐步分解:

  1. LOGGING_CHECK_FUNCTION_IMPL: 接受两个参数。第一个参数是 ::logging::CheckError::Check 函数的调用表达式,第二个参数是 condition

  2. ::logging::CheckError::Check: 这个函数用于执行实际的条件检查。它接收一个字符串(表示 condition 的文本形式)和 __VA_ARGS__(额外的参数,通常用于日志记录)。

  3. #condition: 这个操作符将 condition 表达式转换成一个字符串。在宏展开时,#condition 将变成一个字符串字面量,表示 condition 的源代码文本。

  4. __VA_OPT__(, ): 这个表达式利用了我们之前讨论过的 __VA_OPT__ 宏。它会在 __VA_ARGS__ 存在且非空时插入一个逗号。这里的逗号是必需的,因为它用于分隔 #condition__VA_ARGS__

  5. __VA_ARGS__: 这是一个可变参数包,可以接收零个或多个参数。这些参数通常用于日志记录,提供关于失败条件的更多信息。

CHECK宏的使用示例:

例如这样使用 CHECK 宏:

int x = 1;
int y = 2;
CHECK(x == y, "x should be equal to y, but x is %d and y is %d", x, y);

宏的展开:

CHECK 展开后的代码大致如下:

LOGGING_CHECK_FUNCTION_IMPL(
    ::logging::CheckError::Check("x == y" __VA_OPT__(, ) "x should be equal to y, but x is %d and y is %d", x, y),
    x == y);

由于 __VA_ARGS__ 不为空,__VA_OPT__ 会插入一个逗号,所以 __VA_OPT__(, )展开后的代码变成,

LOGGING_CHECK_FUNCTION_IMPL(
    ::logging::CheckError::Check("x == y", "x should be equal to y, but x is %d and y is %d", x, y),
    x == y);

这将调用 ::logging::CheckError::Check 函数,并传递两个参数:一个是 x == y 的字符串表示,另一个是错误消息和附加参数。同时,它还会检查 x == y 是否为真。如果条件不满足,通常会导致程序中断并输出错误信息。

这种模式在许多日志和调试框架中都很常见。

头脑风暴:其他使用场景

条件性插入文本:

你可以使用 VA_OPT 来基于 VA_ARGS 是否为空来决定是否插入某些文本。

#define LOG_PREFIX(prefix, ...) LOG_IMPL(prefix __VA_OPT__(:) __VA_ARGS__)
#define LOG_IMPL(prefix, ...) printf("%s %s\n", prefix, __VA_ARGS__)

LOG_PREFIX("INFO", "Something happened"); // 扩展后为 LOG_IMPL("INFO:" "Something happened")

避免语法错误:

在某些情况下,VA_OPT 可以用于避免宏展开导致的语法错误,比如避免连续的运算符。例如,避免连续的 && 或 || 运算符:

#define AND(...) __VA_ARGS__ __VA_OPT__(&&) __VA_ARGS__

bool result = AND(a > b) AND(c < d); // 扩展后为 bool result = (a > b) && (c < d);

这些场景展示了 VA_OPT 的灵活性,它不仅限于插入逗号,还可以用来控制宏展开时的任何文本或符号的条件性插入。

你还能想到什么__VA_OPT__ 的巧妙用法?来评论区留言吧!

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值