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
#endif

During 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_include only verifies presence; it does not actually include the target file. The #include directive must still be explicitly invoked inside the corresponding #if branch.
  • Macro Expansion: If the argument provided to __has_include is a macro, the macro is expanded prior to file searching.
  • Safe Fallbacks: Unlike an unconditioned #include directive—which causes a fatal compilation error if the file is missing—__has_include fails silently by returning 0, allowing the preprocessor to proceed to an #elif or #else branch.

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
#endif

This construct prevents older preprocessors from interpreting __has_include as an undefined identifier, ensuring safe conditional compilation across mixed-standard codebases.