Spec-Zone.ru › C++

Условное включение

Препроцессор поддерживает условное включение частей исходного файла. Это поведение управляется следующими директивами.

Синтаксис

#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)
1) Включает управляемый блок кода, если constant-expression вычисляется в ненулевое значение.
2) Эквивалентно #if defined identifier .
3) Эквивалентно #if !defined identifier .
4) Включает управляемый блок кода, если предыдущий блок #if и все промежуточные блоки #elif пропущены, а constant-expression вычисляется в ненулевое значение.
5) Эквивалентно #elif defined identifier .
6) Эквивалентно #elif !defined identifier .
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 проверяют указанное условие (см. ниже):

  • Если оно истинно (имеет ненулевое значение), управляемый блок кода включается. В этом случае все последующие блоки #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 выражения (см. ниже), определяющие, задан ли идентификатор как имя макроса.
  • __has_include выражения, определяющие, существует ли заголовочный файл или исходный файл.
(начиная с C++17)
  • __has_cpp_attribute выражения, определяющие, поддерживается ли указанный токен атрибута и какая версия поддерживается.
(начиная с C++20)
  • __has_embed выражения, определяющие, доступен ли ресурс для встраивания.
(начиная с C++26)

defined выражения

defined identifier (1)
defined( identifier ) (2)

Выражение defined вычисляется в 1, если identifier в данный момент задан как имя макроса, и в 0, если это не так.

Идентификатор считается заданным как имя макроса в контексте директивы условного включения, если выполняется любое из следующих условий:

  • Для него имеется одно или несколько активных определений макроса.
  • Он является предопределённым.
  • Он является __has_include.
(начиная с C++17)
  • Он является __has_cpp_attribute.
(начиная с C++20)
  • Он является __has_embed.
(начиная с 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

Если при подстановке макросов между открывающей и закрывающей скобками выражения __has_embed встречается идентификатор limit, prefix, suffix или if_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” is defined as a macro
#if HAS_EMBED("e.dat")             // macro expansion encounters the macro “limit”
(начиная с C++26)

Вычисление условия

После подстановки всех макросов и вычисления описанных выше выражений, используемых только при обработке препроцессором, каждый оставшийся идентификатор, кроме true и false, заменяется на число препроцессора 0 (это относится и к идентификаторам, которые лексически являются ключевыми словами, но не к альтернативным токенам, таким как and).

Затем каждый токен препроцессора преобразуется в токен. Полученные токены образуют условие, управляющее соответствующим блоком кода; оно вычисляется как константное выражение со следующими изменениями:

  • При преобразовании токенов и вычислении условия (включая определение значения символьных литералов):
  • int ведёт себя так, как если бы имел то же представление, что и long.
  • unsignedint ведёт себя так, как если бы имел то же представление, что и unsignedlong.
(до C++11)
  • Все типы знаковых целых чисел ведут себя так, как если бы имели то же представление, что и std::intmax_t.
  • Все типы беззнаковых целых чисел ведут себя так, как если бы имели то же представление, что и std::uintmax_t.
(начиная с 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API