文本宏替换
预处理器支持文本宏替换。还支持类函数文本宏替换。
语法
1 | #define | 标识符 | replacement-list | (可选) | |||
2 | #define | 标识符 | (parameters) | replacement-list | (可选) | ||
3 | #define | 标识符 | (parameters, ...) | replacement-list | (可选) | (自 C++11 起) | |
4 | #define | 标识符 | (...) | replacement-list | (可选) | (自 C++11 起) | |
5 | #undef | 标识符 |
解释
#define 指令
The
对象类宏
对象类宏会将已定义的 标识符 的每个出现都替换为 replacement-list。版本 (1) 的
函数类宏
函数类宏会将已定义的 标识符 的每个出现替换为 replacement-list,此外还会接收一系列参数,这些参数随后会替换 replacement-list 中任何 参数 的相应出现。
函数类宏调用的语法与函数调用语法类似:宏名称后跟一个 (
作为下一个预处理标记的每个实例都会引入被 replacement-list 替换的标记序列。该序列由匹配的 )
标记终止,会跳过中间匹配的括号对。
对于版本 (2),参数的数量必须与宏定义中的参数数量相同。对于版本 (3,4),参数的数量不得少于参数的数量(不包括 ... (自 C++20 起))。否则程序格式错误。如果标识符不是函数表示法,即其后没有括号,则根本不会替换它。
版本 (2) 的
版本 (3) 的
版本 (4) 的
对于版本 (3,4),replacement-list 可以包含标记序列 __VA_OPT__
(content),如果 _VA_ARGS_ 非空,则此序列会被 content 替换,否则扩展为空。
#define F(...) f(0 __VA_OPT__(,) __VA_ARGS__)
F(a, b, c) // replaced by f(0, a, b, c)
F() // replaced by f(0)
#define G(X, ...) f(0, X __VA_OPT__(,) __VA_ARGS__)
G(a, b, c) // replaced by f(0, a, b, c)
G(a, ) // replaced by f(0, a)
G(a) // replaced by f(0, a)
#define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ })
SDEF(foo); // replaced by S foo;
SDEF(bar, 1, 2); // replaced by S bar = { 1, 2 };
注意:如果函数类宏的参数包含未被匹配的括号对保护的逗号(最常见于模板参数列表,例如 assert(std::is_same_v<int, int>);
或 BOOST_FOREACH(std::pair<int, int> p, m))
),则逗号将被解释为宏参数分隔符,导致参数计数不匹配而编译失败。
扫描和替换
- 扫描会跟踪它们替换的宏。如果扫描发现匹配该宏的文本,它会将其标记为“忽略”(所有扫描都会忽略它)。这可以防止递归。
- 如果扫描发现函数类宏,则参数会在放入 replacement-list 之前进行扫描。除非
# 和## 运算符在不扫描的情况下获取参数。 - 宏被替换后,结果文本会被扫描。
注意,可以定义伪递归宏
#define EMPTY
#define SCAN(x) x
#define EXAMPLE_() EXAMPLE
#define EXAMPLE(n) EXAMPLE_ EMPTY()(n-1) (n)
EXAMPLE(5)
SCAN(EXAMPLE(5))
EXAMPLE_ ()(5 -1) (5)
EXAMPLE_ ()(5 -1 -1) (5 -1) (5)
保留的宏名称
包含 标准库头文件 的翻译单元不得 #define 或 #undef 在任何 标准库头文件 中声明的名称。
使用标准库任何部分的翻译单元不允许 #define 或 #undef 与以下名称在词法上相同的名称:
- 具有特殊含义的标识符
- 任何标准属性标记,但 likely 和 unlikely 可以定义为函数类宏 (自 C++20 起)
否则,行为是未定义的。
# 和 ## 运算符
在函数类宏中,replacement-list 中标识符前的 # 运算符会处理该标识符的参数替换,并将其结果包含在引号中,从而有效地创建字符串字面量。此外,预处理器会添加反斜杠来转义嵌入的字符串字面量(如果有)的引号,并根据需要加倍字符串中的反斜杠。所有前导和尾随空格都会被删除,中间的任何空格序列(但在嵌入的字符串字面量内部不会)都会被折叠成单个空格。此操作称为“字符串化”。如果字符串化的结果不是有效的字符串字面量,则行为是未定义的。
当 # 出现在 _VA_ARGS_ 前时,整个展开的 _VA_ARGS_ 会被包含在引号中
#define showlist(...) puts(#__VA_ARGS__)
showlist(); // expands to puts("")
showlist(1, "x", int); // expands to puts("1, \"x\", int")
replacement-list 中任意两个连续标识符之间的 ## 运算符会对这两个标识符(它们首先不会被宏展开)进行参数替换,然后连接结果。此操作称为“连接”或“标记粘贴”。只有能够形成有效标记的标记才能被粘贴:形成更长标识符的标识符、形成数字的数字,或形成 += 的运算符 + 和 =。通过粘贴 / 和 * 无法创建注释,因为在考虑宏替换之前,注释会从文本中移除。如果结果以匹配 通用字符名语法的序列开头,则行为是未定义的。此确定不考虑 翻译阶段 3 中通用字符名的替换。 (自 C++23 起) 如果连接结果不是有效的标记,则行为是未定义的。
注意:某些编译器提供了一个扩展,允许 ## 出现在逗号之后和 __VA_ARGS__ 之前,在这种情况下,当存在可变参数时 ## 不起作用,但当不存在可变参数时会删除逗号:这使得定义如 fprintf (stderr, format, ##__VA_ARGS__)
这样的宏成为可能。 使用 _VA_OPT_ 也可以以标准方式实现此目的,例如 fprintf (stderr, format _VA_OPT_(, ) __VA_ARGS__)。 (自 C++20 起)
#undef 指令
The
预定义宏
以下宏名称在每个翻译单元中都是预定义的
pub | __cplusplus | 表示正在使用的 C++ 标准版本,扩展为值
|
pub | __STDC_HOSTED__(C++11) | 如果实现是托管的(在操作系统下运行),则扩展为整数常量 1 ,如果是独立的(在没有操作系统的情况下运行),则扩展为 0 (宏常量) |
pub | __FILE__ | 扩展为当前文件名,作为字符串字面量,可以通过 #line 指令更改 (宏常量) |
pub | __LINE__ | 扩展为源文件名行号,一个整数常量,可以通过 #line 指令更改 (宏常量) |
pub | __DATE__ | 扩展为翻译日期,一个字符串字面量,形式为 "Mmm dd yyyy" 。"dd" 的第一个字符如果是小于 10 的月份日期,则为一个空格。月份名称与由 std::asctime() 生成的名称相同。(宏常量) |
pub | __TIME__ | 扩展为翻译时间,一个字符串字面量,形式为 "hh:mm:ss" (宏常量) |
pub | __STDCPP_DEFAULT_NEW_ALIGNMENT__(C++17) | 扩展为 std::size_t 字面量,其值为调用不考虑对齐的 operator new 所保证的对齐(较大的对齐将传递给考虑对齐的重载,例如
(宏常量) |
pub | __STDCPP_BFLOAT16_T__ __STDCPP_FLOAT16_T__ __STDCPP_FLOAT32_T__ (C++23) __STDCPP_FLOAT64_T__ __STDCPP_FLOAT128_T__ | 如果实现支持相应的 扩展浮点类型,则扩展为 1 ,否则不扩展。(宏常量) |
以下附加宏名称可能由实现预定义
pub | __STDC__ | 实现定义的,如果存在,通常用于指示 C 兼容性 (宏常量) |
pub | __STDC_VERSION__(C++11) | 实现定义的,如果存在 (宏常量) |
pub | __STDC_ISO_10646__(C++11) | 如果 wchar_t 使用 Unicode,则扩展为形式为 yyyymmL 的整数常量,该日期表示所支持的 Unicode 的最新修订版 (直到 C++23)实现定义的,如果存在 (自 C++23 起) (宏常量) |
pub | __STDC_MB_MIGHT_NEQ_WC__(C++11) | 如果 对于基本字符集成员可能为 false,例如在将 Unicode 用于 wchar_t 的 EBCDIC 系统上(宏常量) |
pub | __STDCPP_THREADS__(C++11) | 如果程序可以有多个执行线程,则扩展为 1 (宏常量) |
pub | __STDCPP_STRICT_POINTER_SAFETY__(C++11) (C++23 中已移除) | 如果实现具有严格的 std::pointer_safety,则扩展为 1 (宏常量) |
这些宏的值(__FILE__ 和 __LINE__ 除外)在翻译单元的整个过程中保持不变。尝试重新定义或取消定义这些宏会导致未定义的行为。
注意:在每个函数体作用域内,有一个特殊的函数局部预定义变量名为 __func__
,定义为一个静态字符数组,其中包含以实现定义格式显示的函数名称。它不是预处理器宏,但它与 __FILE__ 和 __LINE__ 一起使用,例如通过 assert。
语言特性测试宏 (自 C++20 起)
标准定义了一组预处理器宏,对应于 (C++11) 或之后引入的 C++ 语言特性。它们旨在作为一种简单且可移植的方式来检测这些特性的存在。
有关详细信息,请参阅 特性测试。
示例
#include <iostream>
// Make function factory and use it
#define FUNCTION(name, a) int fun_##name() { return a; }
FUNCTION(abcd, 12)
FUNCTION(fff, 2)
FUNCTION(qqq, 23)
#undef FUNCTION
#define FUNCTION 34
#define OUTPUT(a) std::cout << "output: " #a << '\n'
// Using a macro in the definition of a later macro
#define WORD "Hello "
#define OUTER(...) WORD #__VA_ARGS__
int main()
{
std::cout << "abcd: " << fun_abcd() << '\n';
std::cout << "fff: " << fun_fff() << '\n';
std::cout << "qqq: " << fun_qqq() << '\n';
std::cout << FUNCTION << '\n';
OUTPUT(million); //note the lack of quotes
std::cout << OUTER(World) << '\n';
std::cout << OUTER(WORD World) << '\n';
}
SCAN(EXAMPLE(5))
abcd: 12
fff: 2
qqq: 23
34
output: million
Hello World
Hello WORD World
缺陷报告
以下改变行为的缺陷报告已追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 发布时的行为 | 正确行为 |
---|---|---|---|
LWG 294 | (C++98) | 包含标准库头文件的翻译单元可以包含 定义其他标准库头文件中已声明的名称的宏 | 禁止 |