JSON_HAS_CPP_11/14/17/20/23/26 宏详解:nlohmann/json 如何检测并强制指定 C++ 语言标准
2026/9/9 13:49:03 网站建设 项目流程

JSON_HAS_CPP_11/14/17/20/23/26 宏详解:nlohmann/json 如何检测并强制指定 C++ 语言标准

【免费下载链接】jsonJSON for Modern C++项目地址: https://gitcode.com/GitHub_Trending/js/json

nlohmann/json(JSON for Modern C++)以 C++11 为最低基线,但对 C++17、C++20、C++23 乃至 C++26 的新特性提供了渐进式支持。本文聚焦该库的JSON_HAS_CPP_11JSON_HAS_CPP_14JSON_HAS_CPP_17JSON_HAS_CPP_20JSON_HAS_CPP_23JSON_HAS_CPP_26六个预处理器宏,结合源码讲解它们如何被自动检测、在库中扮演什么角色,以及何时需要开发者手动覆盖、如何正确覆盖。读完你将掌握这套「C++ 标准开关」的使用场景与边界,能够在编译器特性检测不准时可靠地驱动 nlohmann/json 的条件编译行为。

宏的用途:一套贯穿全库的标准版本开关

这些宏并不存在于公开 API 中,而是库内部实现 C++ 标准特性条件编译的“总闸”。nlohmann/json 本身是 header-only 且面向 C++11 编译的,但对后续标准引入的库组件提供了可选支持,例如:

  • C++14:泛型 lambda、std::index_sequence等元编程基础,见 detail/meta/cpp_future.hpp 与 ordered_map.hpp 中的#ifdef JSON_HAS_CPP_14分支;
  • C++17std::string_viewstd::optionalstd::filesystem(对应 json_has_filesystem 宏体系);
  • C++20:concepts、<=>三路比较、std::formatstd::ranges
  • C++26std::is_trivial被弃用后改用std::is_trivially_copyable等特性的兼容处理。

对这类新增特性,库会通过预处理器判断当前使用的 C++ 标准。六个宏的声明原型如下:

#define JSON_HAS_CPP_11 #define JSON_HAS_CPP_14 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_23 #define JSON_HAS_CPP_26

一旦开发者手动定义了其中任意一个宏,库内置的“自动检测”将被整体跳过,并以开发者提供的 C++ 版本为准(无条件假设)。这一机制对“只实现了标准的一部分、会被检测逻辑误判”的编译器尤其有用。

默认检测逻辑:__cplusplus_MSVC_LANG_HAS_CXX17

在开发者未手动定义任何JSON_HAS_CPP_*宏时,库依据编译器的标准版本宏自动完成检测,涉及的主要宏包括__cplusplus_MSVC_LANG(MSVC 在未完全遵循__cplusplus时使用)、_HAS_CXX17_HAS_CXX14

完整的检测算法位于 include/nlohmann/detail/macro_scope.hpp 的头部(约第 33–59 行),采用“由高到低”的阶梯式判断:一旦某个更高的标准阈值成立,就把从该标准向下到 C++14 的所有宏一次性定义齐:

判定条件(满足其一)自动定义的宏
__cplusplus > 202302L_MSVC_LANG > 202302LJSON_HAS_CPP_26JSON_HAS_CPP_23JSON_HAS_CPP_20JSON_HAS_CPP_17JSON_HAS_CPP_14
__cplusplus > 202002L_MSVC_LANG > 202002LJSON_HAS_CPP_23JSON_HAS_CPP_20JSON_HAS_CPP_17JSON_HAS_CPP_14
__cplusplus > 201703L_MSVC_LANG > 201703LJSON_HAS_CPP_20JSON_HAS_CPP_17JSON_HAS_CPP_14
__cplusplus > 201402L_HAS_CXX17 == 1JSON_HAS_CPP_17JSON_HAS_CPP_14
__cplusplus > 201103L_HAS_CXX14 == 1JSON_HAS_CPP_14

检测结束后,无论命中哪一档,JSON_HAS_CPP_11都会被无条件定义——因为 C++11 是库支持的最低编译标准:

// the cpp 11 flag is always specified because it is the minimal required version #define JSON_HAS_CPP_11

源码中一个值得注意的实现细节是_HAS_CXX17分支处标注了// fix for issue #464:部分 MSVC 环境下仅依据__cplusplus会误判 C++17 支持情况,因此库额外读取 MSVC 的_HAS_CXX17宏来修正该问题。这也正是“编译器只实现了标准的一部分、导致检测不准”这一真实场景的例证。

六个宏在源码中的实际作用点

了解了宏的定义方式后,再看它们在核心实现中的消费方式,可以更清楚手动覆盖会影响到哪些能力。

C++17 相关:std::string_viewstd::optional

主头文件 include/nlohmann/json.hpp 在第 75–80 行通过#if defined(JSON_HAS_CPP_17)决定是否引入<string_view>(以及在启用静态 RTTI 时的<any>):

#if defined(JSON_HAS_CPP_17) #if JSON_HAS_STATIC_RTTI #include <any> #endif #include <string_view> #endif

与此同时,detail/conversions/from_json.hpp 在第 35–71 行依据#ifdef JSON_HAS_CPP_17才定义std::optional<T>的反序列化转换——JSONnull映射到std::nullopt、非空值则emplaceT

#ifdef JSON_HAS_CPP_17 template < typename BasicJsonType, typename T, ... > void from_json(const BasicJsonType& j, std::optional<T>& opt) { if (j.is_null()) { opt = std::nullopt; } else { opt.emplace(j.template get<T>()); } } #endif // JSON_HAS_CPP_17

同样地,detail/conversions/to_json.hpp 与 detail/meta/type_traits.hpp 中也大量存在JSON_HAS_CPP_17条件分支,用于判别std::optionalstd::string_view等类型的特化路径。

C++20 相关:concepts、三路比较与std::swap特化

  • detail/input/input_adapters.hpp 中,当__cpp_lib_conceptsJSON_HAS_CPP_20同时满足时,会走基于 concepts 的输入适配器约束;
  • include/nlohmann/json.hpp 第 5368–5380 行注释明确指出“C++20 禁止在std命名空间内对函数进行特化”,因此在#ifndef JSON_HAS_CPP_20保护下才提供std::swap的旧式特化,而 C++20 下改用<=>相关的三路比较实现(参见 json_has_three_way_comparison 宏)。

C++26 相关:弃用特性规避

在 detail/output/binary_writer.hpp 第 1891–1896 行,JSON_HAS_CPP_26被用来在 C++26 下规避对std::is_trivial(已弃用)的依赖,改用std::is_trivially_copyablestd::is_trivially_default_constructible完成字符类型的static_assert校验:

#ifdef JSON_HAS_CPP_26 static_assert(std::is_trivially_copyable<CharType>::value, "CharType must be trivially copyable"); static_assert(std::is_trivially_default_constructible<CharType>::value, "CharType must be trivially default constructible"); #else static_assert(std::is_trivial<CharType>::value, "CharType must be trivial"); #endif

由此可见,从 C++11 基线到 C++26 前沿,这些宏贯穿了解析、序列化、类型转换与标准库适配的几乎所有层面;对它们进行手动覆盖,本质上是在告诉整个库“请按我声明的标准版本编译”。

何时需要手动覆盖,以及如何正确覆盖

库文档明确给出的动机是:某些编译器只实现了标准的局部内容,会被自动检测逻辑误判,此时需要由开发者显式声明真实的编译标准。典型用法是在包含头文件之前定义相应宏(示例以 C++14 为例):

#define JSON_HAS_CPP_14 1 #include <nlohmann/json.hpp> // ...

需要注意两点关键语义:

  1. 一旦手动定义任意一个宏,自动检测会被整体跳过(见 macro_scope.hpp 第 35 行最外层的#if !defined(...)保护),库不再补全其余标准的宏;
  2. 因此手动覆盖时必须自行定义所有适用层级的宏,包括JSON_HAS_CPP_11。例如按 C++17 编译却需强制指定时,应当同时给出:
#define JSON_HAS_CPP_11 1 #define JSON_HAS_CPP_14 1 #define JSON_HAS_CPP_17 1 #include <nlohmann/json.hpp>

这与自动检测“逐档向下、直到 C++11 全部点亮”的行为保持一致。如果只定义JSON_HAS_CPP_17,其余低层级宏缺失,可能导致使用这些宏的#ifndef/#ifdef分支(例如需要“非 C++17 回退路径”的代码)进入与真实编译标准不符的状态。

此外,这些宏与库中另一组“特性开关”宏协同工作,例如JSON_HAS_FILESYSTEMJSON_HAS_STATIC_RTTIJSON_HAS_STD_FORMATJSON_HAS_RANGESJSON_HAS_THREE_WAY_COMPARISON(对应文档见 api/macros 目录)。JSON_HAS_CPP_*定义的是“正在使用哪个语言标准”,后者判断的是“当前标准库是否可用某项具体特性”,两者在#if条件中常组合出现。

重要副作用:所有宏在库外被取消定义

这六个宏与库中绝大部分内部宏一样,作用域被严格限定在头文件内部。在json.hpp的收尾阶段会引入 include/nlohmann/detail/macro_unscope.hpp,它执行一系列#undef清理,其中就包括:

#undef JSON_HAS_CPP_11 #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_20 #undef JSON_HAS_CPP_23 #undef JSON_HAS_CPP_26

因此,这些宏并不会“泄漏”到你自己的翻译单元中影响后续代码。唯一的例外是当定义JSON_TEST_KEEP_MACROS时(库测试框架自用),清理动作会被跳过。一个实用的推论是:若你在包含<nlohmann/json.hpp>之后再去#ifdef JSON_HAS_CPP_17,宏已经不存在;若希望在包含之前强制指定标准,则宏必须定义在 include 之前,且对当前编译单元持续生效到头文件末尾。

版本历史

依据官方 API 文档(docs/mkdocs/docs/api/macros/json_has_cpp_11.md)记录的版本演变:

  • 3.10.5:新增JSON_HAS_CPP_11JSON_HAS_CPP_14JSON_HAS_CPP_17JSON_HAS_CPP_20
  • 3.12.0:新增JSON_HAS_CPP_23
  • 3.13.0:新增JSON_HAS_CPP_26

在当前仓库中,macro_scope.hpp的自动检测已包含> 202302L的 C++26 阈值分支,且binary_writer.hpp等文件已实际引用JSON_HAS_CPP_26,说明检测与使用逻辑在代码中已完整落地。

实践建议小结

  • 绝大多数场景无需干预:GCC、Clang 与新版 MSVC 的__cplusplus/_MSVC_LANG均已准确反映实际标准,自动检测足够可靠;
  • 仅在编译器特性支持不完整、被误判时手动覆盖,且覆盖值必须与编译命令中的-std=c++17/std:c++17等真实标准保持一致;
  • 覆盖要成套给出,从JSON_HAS_CPP_11到目标标准逐一定义,避免半套定义造成条件分支错乱;
  • 定义位置务必在首次#include <nlohmann/json.hpp>之前,因为宏在头文件处理结束后即被#undef清理。

理解这六个宏,就等于拿到了 nlohmann/json 条件编译体系的主钥匙——它决定了std::string_view解析、std::optional转换、concepts 适配乃至 C++26 弃用特性规避等能力在你当前编译标准下是否被激活。

【免费下载链接】jsonJSON for Modern C++项目地址: https://gitcode.com/GitHub_Trending/js/json

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询