Ch3.12: #ifdef / #ifndef

Conditional Compilation

The preprocessor can include or exclude parts of your code based on whether a macro is defined. This is called conditional compilation.

It is used for:

#ifdef and #ifndef

#ifdef checks whether a macro is defined. #ifndef checks whether a macro is not defined.


#ifdef DEBUG
print("debug mode\n");
#endif

#ifndef RELEASE
print("not release mode\n");
#endif

Multi-branch Conditional Compilation

You can chain multiple conditions using #elif.


#if defined(FAST_IO)
print("fast_io enabled\n");
#elif defined(USE_SIMD)
print("SIMD enabled\n");
#else
print("default configuration\n");
#endif

Only the first matching branch is included.

Include Guards

Include guards prevent a header file from being included more than once. They are the traditional alternative to #pragma once.


// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

void f();

#endif

When the header is included again, MYHEADER_H is already defined, so the contents are skipped.

Platform-Specific Includes

You can conditionally include headers depending on the platform.


#if defined(_WIN32)
#include 
#undef min
#undef max
#endif

windows.h defines macros min and max, which conflict with C++ functions of the same name. #undef removes these macros so you can use the real functions.

#undef

The #undef directive removes a macro definition.


#define PI 3.14
#undef PI   // PI no longer defined

This is often used to undo macros from system headers.

Conditional Include

Some compilers support __has_include to check whether a header exists before including it.


#if __has_include()
#include 
#endif

This allows portable code that adapts to available headers.

Feature Detection: __has_embed

__has_embed checks whether the #embed directive is supported by the compiler.


#if __has_embed("resource.bin")
static constexpr auto data =
#embed "resource.bin";
#else
print("embed not supported\n");
#endif

This allows portable use of #embed when available.

Assertions and -DNDEBUG

The header <cassert> provides the assert macro. It checks a condition at runtime and aborts the program if the condition is false.


#include <cassert>

void f(int x) {
    assert(x != 0);
    println(100 / x);
}

In debug builds, this prints an error message and terminates the program if x == 0.

Disabling assertions with -DNDEBUG

If the macro NDEBUG is defined, all assert calls are removed by the preprocessor.


// compile with:
// g++ -DNDEBUG main.cpp

assert(x != 0);   // expands to nothing

This is why release builds typically define NDEBUG.

Warning: no side effects inside assert

Because assert disappears when NDEBUG is defined, the expression inside it must not have side effects.


assert(++i != 0);   // BAD: increments only in debug builds

This leads to different behavior between debug and release builds.

Key takeaways