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:
- platform-specific code
- compiler-specific code
- conditional includes
- include guards
- feature detection (
__has_include,__has_embed)
#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
- #ifdef / #ifndef: include or exclude code based on macro definitions.
- #elif: multi-branch conditional compilation.
- Include guards: prevent multiple inclusion of headers.
- Platform-specific includes: include headers only where valid.
- #undef: remove macros (e.g.,
min,maxfromwindows.h). - __has_include: check if a header exists before including it.
- __has_embed: check if
#embedis supported. - assert + NDEBUG: assertions disappear in release builds.
- No side effects in assert: behavior differs between debug and release.
- Conditional compilation happens before C++: the preprocessor only sees tokens.