条件包含
预处理器支持有条件地包含源文件的某些部分。这一行为由以下指令所控制。
语法
#if 常量表达式
|
(1) | ||||||||
#ifdef 标识符
|
(2) | ||||||||
#ifndef 标识符
|
(3) | ||||||||
#elif 常量表达式
|
(4) | ||||||||
#elifdef 标识符
|
(5) | (C++23 起) | |||||||
#elifndef 标识符
|
(6) | (C++23 起) | |||||||
#else
|
(7) | ||||||||
#endif
|
(8) | ||||||||
#if defined 标识符 。#if !defined 标识符 。#if 块和与其之间的所有 #elif 块,且常量表达式 求值为非零值时包含它所控制的代码块。#elif defined 标识符 。#elif !defined 标识符 。#if 块和与其之间的所有 #elif 块时包含它所控制的代码块。#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表达式(见下文),检测某个标识符是否被定义为宏名。
|
(C++17 起) |
|
(C++20 起) |
|
(C++26 起) |
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 标准化,不过鼓励实现将它们作为遵从的扩展向后移植到旧语言模式。
解决 CWG 问题 1955 前,“#if cond1 ... #elif cond2”和“#if cond1 ... #else”后面跟着“#if cond2”是不同的,因为当 cond1 为真时第二个 #if 将被跳过,且 cond2 并不需要是良构的,而 #elif 中的 cond2 则必须是合法的表达式。 CWG 1955 开始,引领跳过的代码块的 #elif 也被跳过。
示例
#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++14 | 要求失败的 #elif 中的表达式合法
|
跳过失败的 #elif
|
参阅
条件包含的 C 文档
|