Условное включение
Препроцессор поддерживает условное включение частей исходного файла. Это поведение управляется следующими директивами.
Синтаксис
#if constant-expression | (1) | |
#ifdef identifier | (2) | |
#ifndef identifier | (3) | |
#elif constant-expression | (4) | |
#elifdef identifier | (5) | (начиная с C++23) |
#elifndef identifier | (6) | (начиная с C++23) |
#else | (7) | |
#endif | (8) |
Пояснение
Блок условной обработки препроцессором начинается с директивы #if, #ifdef или #ifndef, затем может содержать любое количество директив #elif, #elifdef или #elifndef(начиная с C++23), затем может содержать не более одной директивы #else и завершается директивой #endif. Все вложенные блоки условной обработки препроцессором обрабатываются отдельно.
За исключением #endif, каждая из этих директив управляет блоком кода вплоть до первой директивы #elif, #elifdef, #elifndef(начиная с C++23), #else или #endif, не принадлежащей ни одному вложенному блоку условной обработки препроцессором.
Директивы #if, #ifdef и #ifndef проверяют указанное условие (см. ниже):
- Если оно истинно (имеет ненулевое значение), управляемый блок кода включается. В этом случае все последующие блоки #else, #elifdef, #elifndef,(начиная с C++23) и #elif вплоть до соответствующей директивы #endif пропускаются.
- В противном случае управляемый блок кода пропускается. В этом случае:
- Если следующей директивой, не принадлежащей вложенному блоку, является #endif, никакой блок кода не включается.
- Если следующей директивой, не принадлежащей вложенному блоку, является #else, управляемый ею блок кода включается безусловно.
- В противном случае следующая директива, не принадлежащая вложенному блоку (#elif, #elifdef или #elifndef(начиная с C++23)), действует как директива #if: проверяет условие, включает или пропускает управляемый блок кода в зависимости от результата и, если блок пропускается, обрабатывает следующую директиву.
#define MY_MACRO 2 #if MY_MACRO > 0 // included #elif MY_MACRO > 1 // the condition of the #if-block is passed, no more tests // not included #endif
Проверка условий
Выражения, используемые только при обработке препроцессором
constant-expression может содержать следующие выражения:
-
definedвыражения (см. ниже), определяющие, задан ли идентификатор как имя макроса.
| (начиная с C++17) |
| (начиная с C++20) |
| (начиная с C++26) |
defined выражения
defined identifier | (1) | |
defined( identifier ) | (2) |
Выражение defined вычисляется в 1, если identifier в данный момент задан как имя макроса, и в 0, если это не так.
Идентификатор считается заданным как имя макроса в контексте директивы условного включения, если выполняется любое из следующих условий:
- Для него имеется одно или несколько активных определений макроса.
- Он является предопределённым.
| (начиная с C++17) |
| (начиная с C++20) |
| (начиная с C++26) |
Подстановка макросов
За исключением identifier ов в выражениях defined, макросы в constant-expression ах подставляются до вычисления условия.
Если в результате этого процесса подстановки создаётся defined — токен препроцессора — или использование унарного оператора defined до подстановки макросов не соответствует одной из двух указанных форм, поведение не определено(до C++26)программа некорректна, диагностика не требуется(начиная с C++26).
#define MACRO defined // UB until C++26, IFNDR since C++26 #if MACRO MACRO // “defined” is generated by macro expansion #if defined (A + B) // “A + B” is not an identifier #if defined 1 // 1 is a literal, not an identifier #if defined // no argument provided
| Если при подстановке макросов между открывающей и закрывающей скобками выражения #define limit 0
#define HAS_EMBED_LIMIT_10(file) __has_embed(file limit(10))
// ill-formed
#if __has_embed("e.dat" limit(10)) // “limit” is defined as a macro
#if HAS_EMBED("e.dat") // macro expansion encounters the macro “limit”
| (начиная с C++26) |
Вычисление условия
После подстановки всех макросов и вычисления описанных выше выражений, используемых только при обработке препроцессором, каждый оставшийся идентификатор, кроме true и false, заменяется на число препроцессора 0 (это относится и к идентификаторам, которые лексически являются ключевыми словами, но не к альтернативным токенам, таким как and).
Затем каждый токен препроцессора преобразуется в токен. Полученные токены образуют условие, управляющее соответствующим блоком кода; оно вычисляется как константное выражение со следующими изменениями:
- При преобразовании токенов и вычислении условия (включая определение значения символьных литералов):
| (до C++11) |
| (начиная с C++11) |
- Каждый подвыражение типа bool подвергается целочисленному продвижению, прежде чем обработка продолжится.
Если условие вычисляется в ненулевое значение, управляемый блок кода включается; в противном случае он пропускается.
Примечания
Хотя директивы #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
// Note that if a compiler does not support C++23's #elifdef/#elifndef
// directives then the "unexpected" block (see below) will be selected.
#ifdef CPU
std::cout << "4: no1\n";
#elifdef GPU
std::cout << "4: no2\n";
#elifndef RAM
std::cout << "4: yes\n"; // expected block
#else
std::cout << "4: no!\n"; // unexpectedly selects this block by skipping
// unknown directives and "jumping" directly
// from "#ifdef CPU" to this "#else" block
#endif
// To fix the problem above we may conditionally define the
// macro ELIFDEF_SUPPORTED only if the C++23 directives
// #elifdef/#elifndef are 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"; // expected block
#else
std::cout << "4: no3\n";
#endif
#else // when #elifdef unsupported use old verbose “#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"; // expected block
#else
std::cout << "4: no3\n";
#endif
#endif
}
Возможный вывод:
1: yes 2: yes 3: yes 4: no! 4: yes
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применялся к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| CWG 1955 | C++98 |
#elif должно содержать корректное выражение, даже если ему предшествовал блок #if/#elif, условие которого было true | в этом случае выражение в #elif пропускается |
См. также
| Документация по C для условного включения |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/preprocessor/conditional