Using __has_include in C++ to Check Headers
The __has_include preprocessor operator allows C++
programs to detect the availability of a header file before attempting
to include it. Standardized in C++17, this feature enables developers to
write portable, modular code that adapts to different compilers,
operating systems, standard library implementations, and external
dependencies without triggering fatal compilation errors when a header
is missing.
Syntax and Evaluation
The __has_include operator is used strictly within
preprocessor conditional expressions, such as #if or
#elif. It accepts a single header target enclosed in either
angle brackets or double quotation marks:
#if __has_include(<optional>)
# include <optional>
# define HAS_OPTIONAL 1
#elif __has_include(<experimental/optional>)
# include <experimental/optional>
# define HAS_OPTIONAL 1
#else
# define HAS_OPTIONAL 0
#endifDuring preprocessing, the compiler searches for the specified file
using the standard lookup rules. If the target is enclosed in
< >, the preprocessor searches system and specified
include directories. If enclosed in " ", it searches
relative to the current file first, then falls back to include
paths.
If the header exists and can be opened for inclusion,
__has_include evaluates to the integer literal
1. If the header is absent or cannot be resolved, it
evaluates to 0.
Key Operational Characteristics
- Non-Inclusion:
__has_includeonly verifies presence; it does not actually include the target file. The#includedirective must still be explicitly invoked inside the corresponding#ifbranch. - Macro Expansion: If the argument provided to
__has_includeis a macro, the macro is expanded prior to file searching. - Safe Fallbacks: Unlike an unconditioned
#includedirective—which causes a fatal compilation error if the file is missing—__has_includefails silently by returning0, allowing the preprocessor to proceed to an#elifor#elsebranch.
Ensuring Backward Compatibility
Because __has_include is a C++17 feature, using it
directly in environments targeting older C++ standards can cause syntax
errors. To ensure maximum cross-version portability, check whether the
operator itself is defined before calling it:
#if defined(__has_include)
# if __has_include(<filesystem>)
# include <filesystem>
namespace fs = std::filesystem;
# elif __has_include(<experimental/filesystem>)
# include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
# endif
#endifThis construct prevents older preprocessors from interpreting
__has_include as an undefined identifier, ensuring safe
conditional compilation across mixed-standard codebases.