close
Namespaces
Variants

Conditional inclusion

From cppreference.com
 
 
C++ language
General topics
Flow control
Conditional execution statements
if
Iteration statements (loops)
for
range-for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications (until C++17*)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
explicit (C++11)
static

Special member functions
Templates
Miscellaneous
 
 

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)
1) Includes the code block it controls if constant-expression evaluates to a nonzero value.
2) Equivalent to #if defined identifier .
3) Equivalent to #if !defined identifier .
4) Includes the code block it controls if the previous #if block and all intervening #elif blocks are skipped, and constant-expression evaluates to a nonzero value.
5) Equivalent to #elif defined identifier .
6) Equivalent to #elif !defined identifier .
7) Includes the code block it controls if the previous #if block and all intervening #elif blocks are skipped.
8) Terminates the current #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 #elif blocks till the associated #endif directive 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 #if directive: checks for condition, includes or skips the controlled code block based on the result, and in the latter case processes its subsequent directive.
#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:

  • defined expressions (see below), which detects whether an identifier is defined as a macro name.
  • __has_include expressions, which detects whether a header or source file exists.
(since C++17)
  • __has_cpp_attribute expressions, which detects whether a given attribute token is supported and its supported version.
(since C++20)
  • __has_embed expressions, which detects whether a resource is available to be embedded.
(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 is __has_include.
(since C++17)
  • It is __has_cpp_attribute.
(since C++20)
  • It is __has_embed.
(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 limit, prefix, suffix or if_empty is encountered during the macro expansion between the opening and closing parentheses of a __has_embed expression, and the identifier is defined as a macro, the program is ill-formed.

#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):
  • int acts as it has the same representation as long.
  • unsigned int acts as it has the same representation as unsigned long.
(until C++11)
(since C++11)

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 if
it 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