跳到主要内容

文本宏替换

预处理器支持文本宏替换。还支持类函数文本宏替换。

语法

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#define指令将 标识符 定义为宏,即指示编译器将 标识符 的大多数后续出现替换为 replacement-list,后者还将被进一步处理。例外情况源于 扫描和替换 规则。如果 标识符 已被定义为任何类型的宏,则除非定义相同,否则程序格式错误。

对象类宏

对象类宏会将已定义的 标识符 的每个出现都替换为 replacement-list。版本 (1)#define指令的行为与此完全相同。

函数类宏

函数类宏会将已定义的 标识符 的每个出现替换为 replacement-list,此外还会接收一系列参数,这些参数随后会替换 replacement-list 中任何 参数 的相应出现。

函数类宏调用的语法与函数调用语法类似:宏名称后跟一个 ( 作为下一个预处理标记的每个实例都会引入被 replacement-list 替换的标记序列。该序列由匹配的 ) 标记终止,会跳过中间匹配的括号对。

对于版本 (2),参数的数量必须与宏定义中的参数数量相同。对于版本 (3,4),参数的数量不得少于参数的数量(不包括 ... (自 C++20 起))。否则程序格式错误。如果标识符不是函数表示法,即其后没有括号,则根本不会替换它。

版本 (2)#define指令定义了一个简单的函数类宏。

版本 (3)#define指令定义了一个带有可变数量参数的函数类宏。其他参数(称为*可变参数*)可以使用 _VA_ARGS_ 标识符访问,该标识符随后会被替换为提供给要替换的标识符的参数。

版本 (4)#define指令定义了一个带有可变数量参数但没有常规参数的函数类宏。参数(称为*可变参数*)只能使用 _VA_ARGS_ 标识符访问,该标识符随后会被替换为提供给要替换的标识符的参数。

对于版本 (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 };
 (自 C++20 起)

注意:如果函数类宏的参数包含未被匹配的括号对保护的逗号(最常见于模板参数列表,例如 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 与以下名称在词法上相同的名称:

 (自 C++11 起)

否则,行为是未定义的。

### 运算符

在函数类宏中,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")
 (自 C++11 起)

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#undef指令取消定义 标识符,即取消 标识符 先前由#define指令进行的定义。如果标识符没有关联的宏,则忽略该指令。

预定义宏

以下宏名称在每个翻译单元中都是预定义的

pub__cplusplus表示正在使用的 C++ 标准版本,扩展为值
  • 199711L (直到 C++11),
  • 201103L(C++11),
  • 01402L(C++14),
  • 201703L(C++17),
  • 202002L(C++20),或
  • 202302L(C++23)
(宏常量)
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 所保证的对齐(较大的对齐将传递给考虑对齐的重载,例如
operator new(std::size_t, std::align_val_t)

(宏常量)
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)如果
'x' == L'x'
对于基本字符集成员可能为 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++11 起)

语言特性测试宏 (自 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)包含标准库头文件的翻译单元可以包含
定义其他标准库头文件中已声明的名称的宏
禁止

文本宏替换

预处理器支持文本宏替换。还支持类函数文本宏替换。

语法

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#define指令将 标识符 定义为宏,即指示编译器将 标识符 的大多数后续出现替换为 replacement-list,后者还将被进一步处理。例外情况源于 扫描和替换 规则。如果 标识符 已被定义为任何类型的宏,则除非定义相同,否则程序格式错误。

对象类宏

对象类宏会将已定义的 标识符 的每个出现都替换为 replacement-list。版本 (1)#define指令的行为与此完全相同。

函数类宏

函数类宏会将已定义的 标识符 的每个出现替换为 replacement-list,此外还会接收一系列参数,这些参数随后会替换 replacement-list 中任何 参数 的相应出现。

函数类宏调用的语法与函数调用语法类似:宏名称后跟一个 ( 作为下一个预处理标记的每个实例都会引入被 replacement-list 替换的标记序列。该序列由匹配的 ) 标记终止,会跳过中间匹配的括号对。

对于版本 (2),参数的数量必须与宏定义中的参数数量相同。对于版本 (3,4),参数的数量不得少于参数的数量(不包括 ... (自 C++20 起))。否则程序格式错误。如果标识符不是函数表示法,即其后没有括号,则根本不会替换它。

版本 (2)#define指令定义了一个简单的函数类宏。

版本 (3)#define指令定义了一个带有可变数量参数的函数类宏。其他参数(称为*可变参数*)可以使用 _VA_ARGS_ 标识符访问,该标识符随后会被替换为提供给要替换的标识符的参数。

版本 (4)#define指令定义了一个带有可变数量参数但没有常规参数的函数类宏。参数(称为*可变参数*)只能使用 _VA_ARGS_ 标识符访问,该标识符随后会被替换为提供给要替换的标识符的参数。

对于版本 (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 };
 (自 C++20 起)

注意:如果函数类宏的参数包含未被匹配的括号对保护的逗号(最常见于模板参数列表,例如 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 与以下名称在词法上相同的名称:

 (自 C++11 起)

否则,行为是未定义的。

### 运算符

在函数类宏中,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")
 (自 C++11 起)

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#undef指令取消定义 标识符,即取消 标识符 先前由#define指令进行的定义。如果标识符没有关联的宏,则忽略该指令。

预定义宏

以下宏名称在每个翻译单元中都是预定义的

pub__cplusplus表示正在使用的 C++ 标准版本,扩展为值
  • 199711L (直到 C++11),
  • 201103L(C++11),
  • 01402L(C++14),
  • 201703L(C++17),
  • 202002L(C++20),或
  • 202302L(C++23)
(宏常量)
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 所保证的对齐(较大的对齐将传递给考虑对齐的重载,例如
operator new(std::size_t, std::align_val_t)

(宏常量)
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)如果
'x' == L'x'
对于基本字符集成员可能为 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++11 起)

语言特性测试宏 (自 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)包含标准库头文件的翻译单元可以包含
定义其他标准库头文件中已声明的名称的宏
禁止