close

条件包含

来自cppreference.com


 
 
C++ 语言
 
 

预处理器支持有条件地包含源文件的某些部分。这一行为由以下指令所控制。

语法

#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 表达式(见下文),检测某个标识符是否被定义为宏名。
  • __has_include 表达式,检测某个标头或源文件存在。
(C++17 起)
(C++20 起)
  • __has_embed 表达式,检测是否可以嵌入某个资源。
(C++26 起)

defined 表达式

defined 标识符 (1)
defined( 标识符 ) (2)

defined 表达式会在标识符 当前被定义为宏名时求值为 1,否则求值为 0

在条件包含指令的语境中,标识符在满足以下任意条件时被定义为宏名

  • 它是 __has_include
(C++17 起)
  • 它是 __has_cpp_attribute
(C++20 起)
  • 它是 __has_embed
(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         // 未提供参数

如果在 __has_embed 表达式的首尾圆括号之间进行宏展开的过程中碰到了标识符 limitprefixsuffixif_empty,并且该标识符被定义为宏,那么程序非良构。

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

条件求值

在进行所有宏展开和对上述预处理专用表达式的求值后,truefalse 以外的所有剩余标识符都会被替换成预处理数字 0(这包含词法上为关键字的标识符,但不包括如 and 之类的代用记号)。

然后,预处理记号会被转换成记号。这些记号会组成控制关联代码块的条件,该条件会按以下额外要求作为常量表达式求值:

  • 在记号转换和条件求值的过程中(包括确定字符字面量的值):
  • int 表现为具有与 long 相同的表示
  • unsigned int 表现为具有与 unsigned long 相同的表示。
(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 文档