命名空间
变体
操作

替换文本宏

来自 cppreference.cn
 
 
C++ 语言
 
 

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

目录

[编辑] 语法

#define 标识符 替换列表(可选) (1)
#define 标识符(参数) 替换列表(可选) (2)
#define 标识符(参数, ...) 替换列表(可选) (3) (C++11 起)
#define 标识符(...) 替换列表(可选) (4) (C++11 起)
#undef 标识符 (5)

[编辑] 解释

[编辑] #define 指令

#define 指令将标识符定义为宏,即指示编译器将随后出现的大部分标识符替换为替换列表,替换列表将被进一步处理。例外情况源于扫描和替换规则。如果标识符已经被定义为任何类型的宏,除非定义是相同的,否则程序是非良构的。

[编辑] 对象式宏

对象式宏将每次出现的已定义标识符替换为替换列表。版本 (1) 的 #define 指令的行为完全如此。

[编辑] 类函数宏

类函数宏将每次出现的已定义标识符替换为替换列表,此外还接受一些参数,这些参数随后替换替换列表中任何参数的相应出现。

类函数宏调用的语法类似于函数调用的语法:宏名称的每次出现后跟一个 ( 作为下一个预处理记号,引入将被替换列表替换的记号序列。该序列由匹配的 ) 记号终止,跳过中间匹配的左右括号对。

对于版本 (2),实参的数量必须与宏定义中形参的数量相同。对于版本 (3,4),实参的数量不得少于形参的数量(不包括 ...)。否则,程序是非良构的。如果标识符不是函数式表示法,即自身后没有括号,则根本不会被替换。

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

版本 (3) 的 #define 指令定义了一个具有可变数量实参的类函数宏。可以使用 __VA_ARGS__ 标识符访问附加实参(称为可变实参),然后该标识符被替换为与要替换的标识符一起提供的实参。

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

对于版本 (3,4),替换列表可能包含记号序列 __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)),则逗号被解释为宏实参分隔符,导致由于实参数量不匹配而导致的编译失败。

[编辑] 扫描和替换
  • 扫描跟踪它们替换的宏。如果扫描找到与此类宏匹配的文本,则将其标记为“要忽略”(所有扫描都将忽略它)。这可以防止递归。
  • 如果扫描找到类函数宏,则在将实参放入替换列表之前会先扫描实参。### 运算符除外,它们接受未扫描的实参。
  • 宏被替换后,将扫描结果文本。

注意,可以定义伪递归宏

#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 起)

否则,行为是未定义的。

[编辑] ### 运算符

在类函数宏中,替换列表中标识符之前的 # 运算符对标识符执行形参替换,并将结果括在引号中,从而有效地创建字符串字面量。此外,预处理器添加反斜杠来转义嵌入式字符串字面量(如果有)周围的引号,并在字符串中根据需要加倍反斜杠。所有前导和尾随空格都被删除,文本中间的任何空格序列(但不在嵌入式字符串字面量内部)都折叠为单个空格。此操作称为“字符串化”。如果字符串化的结果不是有效的字符串字面量,则行为是未定义的。

# 出现在 __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 起)

替换列表中任何两个连续标识符之间的 ## 运算符对这两个标识符(首先不进行宏展开)执行形参替换,然后连接结果。此操作称为“连接”或“记号粘贴”。只有一起形成有效记号的记号才可以粘贴:形成更长标识符的标识符,形成数字的数字,或形成 += 的运算符 +=。不能通过粘贴 /* 来创建注释,因为在考虑宏替换之前,注释已从文本中删除。如果连接的结果不是有效的记号,则行为是未定义的。

注意:某些编译器提供了一个扩展,允许 ## 出现在逗号之后和 __VA_ARGS__ 之前,在这种情况下,当存在可变实参时,## 不执行任何操作,但在不存在可变实参时删除逗号:这使得可以定义诸如 fprintf (stderr, format, ##__VA_ARGS__) 之类的宏。 这也可以使用 __VA_OPT__ 以标准方式实现,例如 fprintf (stderr, format __VA_OPT__(, ) __VA_ARGS__)(C++20 起)

[编辑] #undef 指令

#undef 指令取消定义标识符,即取消先前由 #define 指令对标识符的定义。如果标识符没有关联的宏,则忽略该指令。

[编辑] 预定义宏

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

__cplusplus
表示正在使用的 C++ 标准版本,展开为值
  • 199711L(C++11 前)
  • 201103L(C++11)
  • 201402L(C++14)
  • 201703L(C++17)
  • 202002L(C++20),或
  • 202302L(C++23)
    (宏常量)
__STDC_HOSTED__
(C++11)
如果实现是宿主式的(在操作系统下运行),则展开为整数常量 1,如果实现是独立式的(在没有操作系统的情况下运行),则展开为 0
(宏常量)
__FILE__
展开为当前文件的名称,作为字符字符串字面量,可以通过 #line 指令更改
(宏常量)
__LINE__
展开为当前物理源代码行的行号,一个整数常量,可以通过 #line 指令更改
(宏常量)
__DATE__
展开为翻译日期,格式为 "Mmm dd yyyy" 的字符字符串字面量。如果月份的日期小于 10,则 "dd" 的第一个字符是空格。月份名称就像由 std::asctime() 生成一样
(宏常量)
__TIME__
展开为翻译时间,格式为 "hh:mm:ss" 的字符字符串字面量
(宏常量)
__STDCPP_DEFAULT_NEW_ALIGNMENT__
(C++17)
展开为 std::size_t 字面量,其值是由对不感知对齐的 operator new 的调用保证的对齐方式(更大的对齐方式将传递给感知对齐的重载,例如 operator new(std::size_t, std::align_val_t)
(宏常量)
__STDCPP_­BFLOAT16_­T____STDCPP_­FLOAT16_­T____STDCPP_FLOAT32_T____STDCPP_FLOAT64_T____STDCPP_FLOAT128_T__
(C++23)
当且仅当实现支持相应的扩展浮点类型时,展开为 1
(宏常量)

以下附加宏名可以由实现预定义

__STDC__
实现定义的值,如果存在,通常用于指示 C 一致性
(宏常量)
__STDC_VERSION__
(C++11)
实现定义的值,如果存在
(宏常量)
__STDC_ISO_10646__
(C++11)

如果 wchar_t 使用 Unicode,则展开为 yyyymmL 形式的整数常量,日期指示支持的 Unicode 的最新修订版

(C++23 前)

实现定义的值,如果存在

(C++23 起)

(宏常量)
__STDC_MB_MIGHT_NEQ_WC__
(C++11)
如果对于基本字符集的成员,'x' == L'x' 可能为假,例如在使用 Unicode for wchar_t 的基于 EBCDIC 的系统上,则展开为 1
(宏常量)
__STDCPP_THREADS__
(C++11)
如果程序可以有多个执行线程,则展开为 1
(宏常量)
__STDCPP_STRICT_POINTER_SAFETY__
(C++11)(C++23 中移除)
如果实现具有严格的 std::pointer_safety,则展开为 1
(宏常量)

这些宏的值(__FILE____LINE__ 除外)在整个翻译单元中保持不变。尝试重新定义或取消定义这些宏会导致未定义的行为。

语言特性测试宏

标准定义了一组预处理器宏,这些宏对应于 C++11 或更高版本中引入的 C++ 语言特性。它们旨在作为一种简单且可移植的方式来检测这些特性的存在。

有关详细信息,请参阅特性测试

(C++20 起)


注释

函数局部预定义变量 __func__ 不是预定义宏,但它通常与 __FILE____LINE__ 一起使用,例如由 assert 使用。

(C++11 起)

[编辑] 示例

#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';
}

输出

abcd: 12
fff: 2
qqq: 23
34
output: million
Hello World
Hello WORD World

[编辑] 缺陷报告

以下行为变更缺陷报告已追溯应用于先前发布的 C++ 标准。

DR 应用于 已发布行为 正确行为
CWG 2908 C++98 不清楚 __LINE__ 是否展开为当前的
物理行号或当前逻辑行号
展开为当前的
物理行号
LWG 294 C++98 包含标准库头文件的翻译单元可能包含
定义在其他标准库头文件中声明的名称的宏
禁止
P2621R2 C++23 通用字符名称是不允许的
通过记号连接形成
允许

[编辑] 参见

C++ 文档,关于 宏符号索引
C 文档,关于 替换文本宏