条件包含 - cppreference.com
来自cppreference.com
预处理器支持有条件地包含源文件的某些部分。这一行为由以下指令所控制。
语法
#if 常量表达式
|
(1) | ||||||||
#ifdef 标识符
|
(2) | ||||||||
#ifndef 标识符
|
(3) | ||||||||
#elif 常量表达式
|
(4) | ||||||||
#elifdef 标识符
|
(5) | (C++23 起) | |||||||
#elifndef 标识符
|
(6) | (C++23 起) | |||||||
#else
|
(7) | ||||||||
#endif
|
(8) | ||||||||
1) 在常量表达式 求值为非零值时包含它所控制的代码块。
2) 等价于 #if defined 标识符 。
3) 等价于 #if !defined 标识符 。
4) 在跳过了上一个 #if 块和与其之间的所有 #elif 块,且常量表达式 求值为非零值时包含它所控制的代码块。
5) 等价于 #elif defined 标识符 。
6) 等价于 #elif !defined 标识符 。
7) 在跳过了上一个 #if 块和与其之间的所有 #elif 块时包含它所控制的代码块。
8) 终止当前 #if、#elif 或 #else 块。
解释
条件预处理块由 #if、#ifdef 或 #ifndef 指令开始,然后可选地包含任意多个 #elif、#elifdef 或 #elifndef(C++23 起) 指令,接下来是最多一个可选的 #else 指令,并以 #endif 指令结束。嵌套的条件预处理块会被单独处理。
除 #endif 外,各个指令所控制的代码块在第一个非嵌套的条件预处理块的 #elif、#elifdef、#elifndef(C++23 起)、#else 或 #endif 指令处结束。
#if、#ifdef 和 #ifndef 指令测试其所指定的条件(见下文):
- 如果条件求值为真(非零值),那么就会包含该指令控制的代码块。此时会跳过直到关联的
#endif指令的后续所有#else、#elifdef、#elifndef(C++23 起) 和#elif块。 - 否则就会跳过该指令控制的代码块。此时:
- 如果下一条非嵌套指令是
#endif,那么就不会包含任何代码块。 - 如果下一条非嵌套指令是
#else,那么就会无条件包含它控制的代码块。 - 否则,下一条非嵌套(
#else、#elifdef或#elifndef(C++23 起))指令会按照与#if指令相同的方式执行:即测试条件是否满足,并根据其结果决定包含或跳过它所控制的代码块,并在后一种情况下继续处理该指令的后续指令。
#define MY_MACRO 2 #if MY_MACRO > 0 // 会被包含 #elif MY_MACRO > 1 // #if 指令的条件已检测通过,不会检测此条件 // 不会被包含 #endif
条件的判断
预处理专用表达式
常量表达式 也可以包含以下表达式:
defined表达式(见下文),检测某个标识符是否被定义为宏名。
defined 表达式
defined 标识符
|
(1) | ||||||||
defined( 标识符 )
|
(2) | ||||||||
defined 表达式会在标识符 当前被定义为宏名时求值为 1,否则求值为 0。
在条件包含指令的语境中,标识符在满足以下任意条件时被定义为宏名:
|
(C++17 起) |
|
(C++20 起) |
|
(C++26 起) |
宏展开
除了 defined 表达式中的标识符 以外,常量表达式 中的宏会在条件求值前展开。
如果在替换过程中生成了预处理记号,或者在替换宏之前一元运算符 defined 未使用以上两种用法,那么行为未定义(C++26 前)程序非良构,不要求诊断(C++26 起)。
#define MACRO defined // C++26前未定义,C++26起非良构 #if MACRO MACRO // 宏展开生成了 “defined” #if defined (A + B) // “A + B” 不是标识符 #if defined 1 // 1 是字面量,不是标识符 #if defined // 未提供参数
|
如果在 #define limit 0 #define HAS_EMBED_LIMIT_10(file) __has_embed(file limit(10)) // ill-formed #if __has_embed("e.dat" limit(10)) // “limit” 被定义为宏 #if HAS_EMBED("e.dat") // 宏展开时碰到了宏 “limit” |
(C++26 起) |
条件求值
在进行所有宏展开和对上述预处理专用表达式的求值后,true 和 false 以外的所有剩余标识符都会被替换成预处理数字 0(这包含词法上为关键字的标识符,但不包括如 and 之类的代用记号)。
然后,预处理记号会被转换成记号。这些记号会组成控制关联代码块的条件,该条件会按以下额外要求作为常量表达式求值:
- 在记号转换和条件求值的过程中(包括确定字符字面量的值):
|
(C++11 前) |
|
(C++11 起) |
当条件求值为非零值时,包含它所控制的代码块,否则跳过该代码块。
注解
#elifdef 与 #elifndef 指令在 C++23 标准化,不过鼓励实现将它们作为遵从的扩展向后移植到旧语言模式。
示例
#define ABCD 2 #include <iostream> int main() { #ifdef ABCD std::cout << "1: yes\n"; #else std::cout << "1: no\n"; #endif #ifndef ABCD std::cout << "2: no1\n"; #elif ABCD == 2 std::cout << "2: yes\n"; #else std::cout << "2: no2\n"; #endif #if !defined(DCBA) && (ABCD < 2*4-3) std::cout << "3: yes\n"; #endif // 注意若编译器不支持 C++23 的 #elifdef/#elifndef 指令则会选择“不期待”块(见后述)。 #ifdef CPU std::cout << "4: no1\n"; #elifdef GPU std::cout << "4: no2\n"; #elifndef RAM std::cout << "4: yes\n"; // 期待的块 #else std::cout << "4: no!\n"; // 由于跳过未知的指令不期待地选择此块 // 并直接从 "#ifdef CPU" “跳”到此 "#else" 块 #endif // 为修复此问题,我们可以条件性地仅若支持 C++23 指令 #elifdef/#elifndef // 才定义 ELIFDEF_SUPPORTED 宏。 #if 0 #elifndef UNDEFINED_MACRO #define ELIFDEF_SUPPORTED #else #endif #ifdef ELIFDEF_SUPPORTED #ifdef CPU std::cout << "4: no1\n"; #elifdef GPU std::cout << "4: no2\n"; #elifndef RAM std::cout << "4: yes\n"; // 期待的块 #else std::cout << "4: no3\n"; #endif #else // 不支持 #elifdef 时使用累赘的旧 “#elif defined” #ifdef CPU std::cout << "4: no1\n"; #elif defined GPU std::cout << "4: no2\n"; #elif !defined RAM std::cout << "4: yes\n"; // 期待的块 #else std::cout << "4: no3\n"; #endif #endif }
可能的输出:
1: yes 2: yes 3: yes 4: no! 4: yes
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
| 缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
|---|---|---|---|
| CWG 1955 | C++98 | #elif 即使跟在条件为 true 的#if/#elif 块之后也必须包含有效表达式
|
在这种情况下会跳过 #elif 的表达式
|