__has_include 是 c++17 起支持的预处理器运算符,用于编译期判断头文件是否存在,返回 1 或 0,仅可在 #if 等条件编译中使用,支持尖括号和双引号形式,但不展开宏、不保证可成功包含或特性可用。

__has_include 是什么,它能干什么
它是一个 C++17 起支持的预处理器运算符,用来在编译期判断某个头文件是否存在。不是运行时检查,也不是 #include 失败后兜底——而是让你提前“问一句”,再决定要不要 #include、用哪个版本的接口、甚至跳过某段代码。典型场景是跨平台或跨标准库版本写兼容代码,比如想用 <span></span> 但又得兼容没实现它的旧 libstdc++。
怎么用:语法和基本套路
必须在预处理阶段用,只能出现在 #if / #ifdef 等条件编译中,不能放在函数体里或作为变量值。返回 1 表示存在,0 表示不存在(注意:不保证可成功包含,只表示路径能被找到)。
-
#if __has_include(<optional>)</optional>—— 检查系统头文件,尖括号形式 -
#if __has_include("my_header.h")—— 检查用户头文件,双引号形式 - 不能混用:
#if __has_include("optional")是错的,不会匹配<optional></optional> - 不能带宏展开:
#define HEAD "optional"然后#if __has_include(HEAD)不合法,预处理器不展开字符串字面量
常见踩坑:为什么写了却没生效
最容易忽略的是编译器支持和标准模式。它不是所有“C++17”编译就自动开——有些老版本 clang/gcc 需要显式开启,而且行为有差异:
- gcc 5.1+ 支持,但默认不启用;需加
-std=c++17或更高,且不能用-std=gnu++17(某些旧版会禁用) - clang 3.9+ 支持,同样依赖
-std=c++17 - MSVC 直到 19.29(VS 2019 16.11)才完整支持,之前版本可能返回 0 即使头文件存在
- 就算
__has_include(<filesystem>)</filesystem>返回 1,也不代表std::filesystem可用——可能只是头文件存在但未实现,还得配合__cpp_lib_filesystem宏判断
一个真实兼容场景:optional 与 boost::optional 共存
你想优先用 std::optional,没有就退到 boost::optional。光靠 try-include 会触发编译错误,而 __has_include 能干净分叉:
立即学习“C++免费学习笔记(深入)”;
#if __has_include(<optional>) # include <optional> # define MY_OPTIONAL_NS std #else # include <boost/optional.hpp> # define MY_OPTIONAL_NS boost #endif <p>using my_opt = MY_OPTIONAL_NS::optional<int>;
注意这里没用 #ifdef __cpp_lib_optional——因为那个宏检测的是语言特性是否启用,不是头文件是否存在;而你真正需要的是“我能不能 #include 这个头”,否则可能 #include <optional></optional> 失败直接中断编译。
真正难的是组合判断:头文件存在 + 特性宏定义 + 编译器实际支持。单独靠 __has_include 只解决第一个环节,漏掉后面两个,照样崩。










