首页
/ JSON for Modern C++ 比较运算详解:nlohmann::json 的 operator>= 规则、实现与 C++20 演进

JSON for Modern C++ 比较运算详解:nlohmann::json 的 operator>= 规则、实现与 C++20 演进

2026-09-07 21:11:53作者:薛曦旖Francesca

本文依据仓库文档 operator_ge.md 撰写,面向需要在 C++ 中直接对 JSON 值执行"大于等于"语义比较的开发者,覆盖三个重载签名、以 !(lhs < rhs) 为核心的推导规则、NaNdiscarded 的特殊处理,以及 C++20 三路比较运算符引入后 operator>= 的编译期行为变化。读完本文,你将能准确判断不同类型 JSON 值之间 >= 的求值结果,并理解底层源码如何保证比较全程不抛异常。

nlohmann::json(本项目,即 "JSON for Modern C++")把 C++ 运算符重载直接应用在 JSON 数据模型上,使数组、对象、数字、字符串等原生 JSON 值可以像内置类型一样参与 ==<<=>>= 比较。其中 operator>= 是一个"派生运算符":它本身不定义独立的元素级比较逻辑,而是建立在 operator< 与类型/数值等价判定之上,这也决定了它的返回语义、NaN 规则与 C++20 兼容策略都和底层实现强相关。

一、函数签名与适用场景

在文档给出的定义中,operator>= 提供三个重载,其原型如下:

// until C++20
bool operator>=(const_reference lhs, const_reference rhs) noexcept;   // (1)

template<typename ScalarType>
bool operator>=(const_reference lhs, const ScalarType rhs) noexcept;  // (2)

template<typename ScalarType>
bool operator>=(ScalarType lhs, const const_reference rhs) noexcept;  // (2)
  • 重载 (1):比较两个 JSON 值 lhsrhs
  • 重载 (2):比较"JSON 值与标量"或"标量与 JSON 值",例如 json >= 4242 >= json,实现方式是把标量转换成一个 basic_json 对象后再按规则 (1) 比较。

const_reference 指向常引用;三个重载均声明为 noexcept,保证比较过程不会抛出异常。适用于按大小/先后排序、范围判定、数值上下界校验等需要非严格不等式语义的场景,例如将配置中的阈值与解析得到的 JSON 数字直接比较:

json threshold = 100;
json value = parsed["score"];
if (value >= threshold) { /* 通过阈值校验 */ }
if (parsed["timeout"] >= 30) { /* 超过下限 */ }

二、比较规则:为什么实现是 !(lhs < rhs)

>= 的语义并非独立实现,而是遵循如下规则:

  1. 当满足以下任一条件时,比较结果恒为 false
    • 任一操作数是 discarded(被丢弃值);
    • 任一操作数是 NaN,且另一操作数是 NaN 或任意其它数字。
  2. 否则返回 !(lhs < rhs),即完全复用 operator< 的定义(文档中也通过指向 operator< 的 See also 交叉引用体现了这一依赖关系)。

也就是说,lhs >= rhs 等价于"不满足 lhs < rhs",这正好与数学上全序关系的互补性质对应——但注意 JSON 值域中 NaNdiscarded 是"无顺序(unordered)"的特殊元素,因此第 1 条规则必须优先于第 2 条执行,否则把无序值与任意值比较可能错误地得到 true

源码佐证:compares_unordered 与运算符实现

include/nlohmann/json.hpp 中,C++17 及更早模式下 operator>= 以友元函数形式实现:

friend bool operator>=(const_reference lhs, const_reference rhs) noexcept
{
    if (compares_unordered(lhs, rhs, true))
    {
        return false;
    }
    return !(lhs < rhs);
}

template<typename ScalarType, typename std::enable_if<
             std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept
{
    return lhs >= basic_json(rhs);
}

template<typename ScalarType, typename std::enable_if<
             std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept
{
    return basic_json(lhs) >= rhs;
}

其中 compares_unordered 静态辅助函数(见 include/nlohmann/json.hpp)集中定义了"无序"判定:

static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept
{
    if ((lhs.is_number_float() && std::isnan(lhs.m_data.m_value.number_float) && rhs.is_number())
            || (rhs.is_number_float() && std::isnan(rhs.m_data.m_value.number_float) && lhs.is_number()))
    {
        return true;
    }
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
    return (lhs.is_discarded() || rhs.is_discarded()) && !inverse;
#else
    static_cast<void>(inverse);
    return lhs.is_discarded() || rhs.is_discarded();
#endif
}

从源码结构可以看到两条独立路径都会让比较短路为 false

  • NaN 与数字相遇:只要一侧是 isnan 的浮点值、另一侧是任意 number 类型(含整数 number_integer、无符号 number_unsigned、浮点 number_float),就判定为无序;
  • discarded:任何 discarded 参与比较即无序。discarded 通常来自使用带回调的解析过程(parser callback 丢弃了某个值),本属于不参与业务比较的内部占位值。

值得注意的一个实现细节是 inverse 形参:在定义 JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 的旧版兼容模式下,若比较经由奇数层"取反/翻转"推导(如 >= 先转为 !(<)),会放宽对 discarded 的无序判定,用于复刻 3.10.x 及更早版本的行为;默认(该宏为 0)情况下 inverse 被忽略,任何 discarded 参与都直接判无序。宏的默认值定义见 include/nlohmann/detail/abi_macros.hpp

三、数值跨类型比较的底层支撑

第 2 条规则里出现的 lhs < rhs 最终会落到类型/值比较的公共实现上。在 include/nlohmann/json.hpp 中,JSON_IMPLEMENT_OPERATOR 宏处理了多种"不同 JSON 类型但语义可比"的情况,典型分支包括:

  • number_integernumber_float:整数先转为浮点再比较;
  • number_unsignednumber_float:无符号整数先转为浮点再比较;
  • number_integernumber_unsigned(以及反向):先检查负号,负数与无符号数大小关系由 -11 这类符号哨兵确定,非负时再按同符号比较。

因此诸如 17 >= 17.0000000000001L 这类"整数对浮点"的 >= 会先把整数 17 转换为浮点,再与高精度浮点值比较,最终得到 false(17 略小于 17.0000000000001)。而当两侧类型完全不同(如字符串与数字)时,operator<=>operator< 会退化为按 value_t 的类型序比较。

四、NaN 语义:数字域内的无序值

文档在 Notes 部分用一个专门提示块强调 NaN 的三种比较结果全部为 false

  1. NaN 与自身比较;
  2. NaN 与另一个 NaN 比较;
  3. NaN 与任意其它数字比较。

这意味着对 JSON 浮点值执行 j >= j 时,若 j 持有 NaN,结果不是数学上理所应当的 true,而是 false——NaN 被视作落在数字全序之外的"无序点"。这一设计在 operator_spaceship.md 中表述为比较结果 std::partial_ordering::unordered,而 >= 这类派生运算符把 unordered 一律折叠为布尔值 false。它与 C++ 标准库对浮点 NaN 的既有约定(如 std::isgreater 之外的传统关系运算符对 NaN 返回 false)保持一致,代码中通过成员函数包装(include/nlohmann/json.hpp)把 compares_unordered 复用到成员运算符路径上。

五、C++20 演进:重写候选与条件移除

文档在函数头明确标注三个原型是 "until C++20" 形态。从 3.11.0 版本起,当以 C++20 标准编译时:

  • 类内新增了成员函数 operator<=>(见 include/nlohmann/json.hpp),返回 std::partial_ordering,类型跨界的数值会先转为公共类型再三路比较;
  • 由于 C++20 运算符重载决议会考虑由 operator<=> 生成的重写候选(rewritten candidate)lhs >= rhs 可以直接被编译器改写为基于三路比较的形式,因此旧有的自由函数式 operator>= 在 3.11.0 起被"条件移除"(conditionally removed)。

具体到本仓库源码:当 JSON_HAS_THREE_WAY_COMPARISON 成立时,operator>= 只在一类特殊情况下仍被保留——用户显式定义 JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 以延续旧版 discarded 排序行为时。此时提供的成员重载位于 include/nlohmann/json.hpp

JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)
bool operator>=(const_reference rhs) const noexcept
{
    if (compares_unordered(rhs, true))
    {
        return false;
    }
    return !(*this < rhs);
}

template<typename ScalarType>
requires std::is_scalar_v<ScalarType>
bool operator>=(ScalarType rhs) const noexcept
{
    return *this >= basic_json(rhs);
}

两个特征值得注意:

  • 它们被 JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) 标记为 deprecated,提示使用者该运算符已被三路比较取代;
  • 模板约束从旧式的 std::enable_if<std::is_scalar<...>::value> 升级为 C++20 Concepts 风格的 requires std::is_scalar_v<ScalarType>,但语义一致。

因此在实际工程中:若目标标准为 C++20 且未定义该兼容宏,代码里书写 j1 >= j2 将由编译器依据 operator<=> 自动完成推导,源码中不存在显式的 >= 符号;若仍需旧式显式行为,则定义宏后使用被弃用的重载。这也是文档版本历史中"Added in version 1.0.0, conditionally removed since C++20 in version 3.11.0"的完整含义。

六、模板参数、复杂度与异常安全

  • 模板参数 ScalarType:必须是满足 std::is_scalar<ScalarType>::value(C++20 路径下为 std::is_scalar_v<ScalarType>)的标量类型,如布尔、整数、浮点数、字符、枚举与指针。库会先构造 basic_json(rhs) 完成类型转换。
  • 返回值:布尔值,表示 lhs 是否大于等于 rhs
  • 异常安全:所有重载均为 noexcept,保证比较操作绝不抛出异常。
  • 复杂度:线性。当两侧都是数组或对象时,比较需要逐元素/逐键值对推进,成本与容器规模成正比;标量类型的比较则退化为 O(1)。

七、完整运行示例

文档配套的示例源码位于 docs/mkdocs/docs/examples/operator__greaterequal.cpp,覆盖了数组、对象、数字、字符串四类 JSON 值的 >= 比较:

#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create several JSON values
    json array_1 = {1, 2, 3};
    json array_2 = {1, 2, 4};
    json object_1 = {{"A", "a"}, {"B", "b"}};
    json object_2 = {{"B", "b"}, {"A", "a"}};
    json number_1 = 17;
    json number_2 = 17.0000000000001L;
    json string_1 = "foo";
    json string_2 = "bar";

    // output values and comparisons
    std::cout << std::boolalpha;
    std::cout << array_1 << " >= " << array_2 << " " << (array_1 >= array_2) << '\n';
    std::cout << object_1 << " >= " << object_2 << " " << (object_1 >= object_2) << '\n';
    std::cout << number_1 << " >= " << number_2 << " " << (number_1 >= number_2) << '\n';
    std::cout << string_1 << " >= " << string_2 << " " << (string_1 >= string_2) << '\n';
}

对应的标准输出(operator__greaterequal.output)为:

[1,2,3] >= [1,2,4] false
{"A":"a","B":"b"} >= {"A":"a","B":"b"} true
17 >= 17.0000000000001 false
"foo" >= "bar" true

逐行解读:

  • 数组 [1,2,3] >= [1,2,4]:逐元素比较到第三个元素时 3 < 4 成立,故 !(lhs < rhs)false
  • 对象:两个对象键集合相同(对象本身无序,比较的是成员值),{"A":"a","B":"b"}{"B":"b","A":"a"} 视为相等,故 >=true
  • 数字:整数 17 转为浮点后仍小于 17.0000000000001L,故为 false
  • 字符串"foo" >= "bar" 依字典序 "foo" 更大,故为 true

该示例同时展示了 std::ostream 的序列化输出与 std::boolalpha 配合的常见调试写法,方便读者直接编译验证本项目单头文件库的比较行为。

八、版本历史与可移植性总结

依据文档版本历史:

版本 变更
1.0.0 三个重载(两个 JSON 值、JSON 对标量、标量对 JSON)全部引入
3.11.0 C++20 下条件移除;默认由 operator<=> 重写候选接管,仅当定义 JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 时保留(并标记 deprecated)的旧实现

实践要点归纳

  1. >=!(<) 的封装,真正的大小语义由 operator< / operator<=> 决定,需阅读比较运算族文档以获得一致的排序规则;
  2. NaNdiscarded 有关的 >= 恒为 false,此类值不应参与排序/边界比较逻辑;
  3. 整数与浮点混合比较会先做类型提升,注意浮点表示精度可能影响边界判断(如示例中的 1717.0000000000001L);
  4. C++20 工程中优先依赖 operator<=> 推导出的重写候选即可,无需显式调用旧 API;
  5. 全部重载 noexcept,复杂度线性,可在性能敏感路径放心使用。

本文对应的权威定义、完整示例与源码实现分别位于 operator_ge.mdoperator__greaterequal.cppinclude/nlohmann/json.hpp,读者可按图索骥深入阅读。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388