首页 > 编程语言 >C++宏魔法:__VA_OPT__操作

C++宏魔法:__VA_OPT__操作

时间:2024-07-19 17:28:29浏览次数:14  
标签:__ VA ARGS OPT 参数 condition

在阅读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__ 的巧妙用法?来评论区留言吧!

标签:__,VA,ARGS,OPT,参数,condition
From: https://blog.csdn.net/hebhljdx/article/details/140433790

相关文章

  • 安川伺服驱动器SGDV-200A01A002000
    安川驱动器工作原理一、工作原理安川驱动器是一种在工业生产过程中广泛使用的电子设备,主要用于控制和调节电机的运转。安川驱动器是由交流电源产生交流电信号,通过变频控制芯片将交流电信号转化为直流电信号,再通过逆变电路将直流电信号转化为可调频率和电压的交流电信号,最终控......
  • 02线性表 - 链表
    这里是只讲干货不讲废话的炽念,这个系列的文章是为了我自己以后复习数据结构而写,所以可能会用一种我自己能够听懂的方式来描述,不会像书本上那么枯燥和无聊,且全系列的代码均是可运行的代码,关键地方会给出注释^_^全文1W+字版本:C++17编译器:Clion2023.3.24暂时只给出代码,不会涉......
  • DC-2靶机通关
        最近一段时间我将会持续更新DC系列的靶机通关过程,大家如果也有正在玩儿这些靶机的可以关注一下作者,欢迎大家一起讨论学习!!!        1.实验环境:攻击机:kali2023.2靶机:DC-22.1扫描网段内的主机:2.2扫描靶机开放端口等信息:这里可以发现主机开放了两个端口一......
  • [Linux命令-网络和安全操作]
    目录Vim编辑器:网络网络配置ifconfig:netstat:wget:从指定的URL下载文件 网络安全 在Linux中,进行网络和安全操作时,我们先简单了解一下Vim编辑器Vim编辑器:vim编辑器有三种模式:命令模式、编辑模式、末行模式模式间切换方法:(1)命令模式下,输入:后,进入末行模式(2)末......
  • 【代码随想录训练营第42期 Day3打卡 LeetCode 203.移除链表元素,707.设计链表,206.反转
    一、做题感受今天是打卡的第三天,前两天题目主要考察数组相关知识,现在已经来到了链表的学习。题目共有三道,都是以考察单链表为主,整体来说难度不大,但是思路很灵活,尤其是反转链表的考察,双指针的新用法。今天做题总体感觉不错,能有自己的思考和理解。二、链表相关知识1.常见链表......
  • Chromium源码阅读(10):了解Log模块
    Chromium许多日志被TraceEvent代替了,因此TraceEvent出现的频率要比Log高很多。但是也有不少场景使用Log。在blink,Log的实现由blink/base提供,而chromium的日志由blink/render/core/base/logging.h提供。一些底层的日志由absel的log模块提供。说实话,日志模块的实现数量有点......
  • 初学js Day01
    JavaScript的由来(js)1995年2月发布的,NetscapeNavigator2浏览器开发一种名为LiveScript的脚本语言。为了赶在发布日期前完成LiveScript的开发,Netscape与Sun公司建立了一个开发联盟,共同开发LiveScript。在NetScapeNavigator2发布前夕,网景为了更好地推广这个脚本语言......
  • C语言函数详解
    函数的概念不同于数学上的函数,在C语言中,函数(function)就是一个完成某项特定任务的一段代码,所以函数也叫子程序。函数的分类库函数为了提高写代码的效率,C语言的国际标准ANSIC规定了一些常用的函数的标准,被称为标准库。不同的编译器厂商根据ANSI提供的标准就给出了一系列函数......
  • uview小程序弹窗表单验证,阻止表单关闭
     设置async-close异步关闭<u-modalref="uModal"v-model="show"width="85%"title="原因"confirm-text="确定":async-close="true"show-cancel-button@confirm="confirm"@cancel="cancel&qu......
  • 无人机遥感图像拼接及处理
    朱老师(副教授)重点国高校及科研院所,长期从事生态系统管理、全球变化生态学、生态模型与遥感、气候变化,生态环境数据处理与分析相关工作。发表SCI/EI论文多篇。主持国家自然科学基金等各类纵向科研项目多个。无人机遥感图像采集流程:无人机遥感监测介绍无人机航线规划设计无人......