Conditional inclusion
The preprocessor supports conditional inclusion of parts of source file. This behavior is controlled by the following directives.
Syntax
#if constant-expression
|
(1) | ||||||||
#ifdef identifier
|
(2) | ||||||||
#ifndef identifier
|
(3) | ||||||||
#elif constant-expression
|
(4) | ||||||||
#elifdef identifier
|
(5) | (since C++23) | |||||||
#elifndef identifier
|
(6) | (since C++23) | |||||||
#else
|
(7) | ||||||||
#endif
|
(8) | ||||||||
#if defined identifier .#if !defined identifier .#if block and all intervening #elif blocks are skipped, and constant-expression evaluates to a nonzero value.#elif defined identifier .#elif !defined identifier .#if block and all intervening #elif blocks are skipped.#if, #elif or #else block.Explanation
The conditional preprocessing block starts with an #if, #ifdef or #ifndef directive, then optionally includes any number of #elif, #elifdef, or #elifndef(since C++23) directives, then optionally includes at most one #else directive and is terminated with #endif directive. Any nested conditional preprocessing blocks are processed separately.
Except for #endif, each of these directives control the code block until the first #elif, #elifdef, #elifndef(since C++23), #else or #endif directive not belonging to any nested conditional preprocessing blocks.
#if, #ifdef and #ifndef directives test the specified condition (see below):
- If it evaluates to true (nonzero value), the controlled code block is included. In this case, all subsequent
#else,#elifdef,#elifndef,(since C++23) and#elifblocks till the associated#endifdirective are skipped. - Otherwise, the controlled code block is skipped. In this case:
- If the subsequent non-nested directive is
#endif, no code block is included. - If the subsequent non-nested directive is
#else, the code block it controls is unconditionally included. - Otherwise, the subsequent non-nested (
#elif,#elifdef, or#elifndef(since C++23)) directive acts as if it was an#ifdirective: checks for condition, includes or skips the controlled code block based on the result, and in the latter case processes its subsequent directive.
- If the subsequent non-nested directive is
#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
Testing conditions
Preprocessing-exclusive expressions
constant-expression may contain the following expressions:
definedexpressions (see below), which detects whether an identifier is defined as a macro name.
|
(since C++17) |
|
(since C++20) |
|
(since C++26) |
defined expressions
defined identifier
|
(1) | ||||||||
defined( identifier )
|
(2) | ||||||||
The defined expression evaluates to 1 if identifier is currently defined as macro name, or 0 if it is not.
An identifier is defined as macro name in the context of condition inclusion directive if any of the following conditions is satisfied:
- It has one or more active macro definitions.
- It is predefined.
|
(since C++17) |
|
(since C++20) |
|
(since C++26) |
Macro expansion
Except for the identifier s of defined expressions, macros in constant-expression s will be expanded prior to condition evaluation.
If the preprocessing token defined is generated as a result of this replacement process, or use of the defined unary operator does not match one of the two specified forms prior to macro replacement, the behavior is undefined(until C++26)the program is ill-formed, no diagnostic required(since 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
|
If an identifier #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”
|
(since C++26) |
Condition evaluation
After all macro expansion and evaluations of the preprocessing-exclusive expressions described above, each remaining identifier other than true and false is replaced with the preprocessing number 0 (this includes identifiers that are lexically keywords, but not alternative tokens like and).
After that, each preprocessing token is converted into a token. The resulting tokens comprise the condition controlloing the associated code block, which is evaluated as an constant expression, with the following changes:
- During token conversion and condition evaluation (including determining the value of character literals):
|
(until C++11) |
|
(since C++11) |
- Each subexpression with type
boolis subjected to integral promotion before processing continues.
If the condition evaluates to nonzero value, the controlled code block is included and skipped otherwise.
Notes
While #elifdef and #elifndef directives target C++23, implementations are encouraged to backport them to the older language modes as conforming extensions.
Example
#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
}
Possible output:
1: yes
2: yes
3: yes
4: no!
4: yes
Defect reports
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
| DR | Applied to | Behavior as published | Correct behavior |
|---|---|---|---|
| CWG 1955 | C++98 | #elif must have a valid expression even ifit followed a #if/#elif block whose condition was true
|
the expression of #elif is skipped in this case
|
See also
C documentation for Conditional inclusion
|