跳转至

cereal 和 nlohmann-json 的奇怪冲突

问题描述

最近在处理一个对象同时支持 cerealnlohmann-json 序列化时,遇到了一个奇怪的问题。具体来说,当定义了序列化函数,同时支持这两种序列化库时,程序会出现编译错误。

下面是简化后的代码示例:

#include <cereal/archives/binary.hpp>
#include <nlohmann/json.hpp>
#include <sstream>
#include <string>

struct Waypoint
{
    int id{0};

    // cereal 序列化函数
    void serialize(auto &ar, [[maybe_unused]] uint32_t version) { ar(id); }
};

// nlohmann-json 序列化函数
void to_json(nlohmann::json &j, const Waypoint &wp)
{
    j = nlohmann::json{wp.id};
}

void from_json(const nlohmann::json &j, Waypoint &wp) { wp.id = j.get<int>(); }

// cereal 与 nlohmann-json 的 save 和 load 函数
void save(auto &ar, const nlohmann::json &j) { ar(j.dump()); }

void load(auto &ar, nlohmann::json &j)
{
    std::string s;
    ar(s);
    j = nlohmann::json::parse(s);
}

int main()
{
    std::stringstream ss;
    Waypoint wp1{.id = 1};

    // 使用 cereal 序列化
    {
        cereal::BinaryOutputArchive oar{ss};
        oar(wp1);
    }

    Waypoint wp2;

    // 使用 cereal 反序列化
    {
        cereal::BinaryInputArchive iar{ss};
        iar(wp2);
    }
}

当编译上述代码时,你可能会遇到如下错误信息:

cereal found more than one compatible output serialization function for the provided type and archive combination...

通过对 cereal 源码的深入分析,逐步确定了问题所在。原因在于,void save(auto &ar, const nlohmann::json &j) 这个函数和 Waypoint 类内的 serialize 函数都可以被调用来序列化 Waypoint。具体来说,Waypoint 定义的 to_json 函数让其可以隐式转换为 nlohmann::json,而 nlohmann::json 又定义了一个 cerealsave 函数。由于这两个函数之间的重叠,cereal 序列化机制无法决定使用哪个函数,从而导致了编译错误。

解决方法

为了解决这个问题,可以通过偏特化模板来调整 nlohmann::jsonsaveload 函数,从而避免冲突,并且同时支持 nlohmann::jsonnlohmann::ordered_json

template <template <typename...> class ObjectType>
void save(auto& ar, const nlohmann::basic_json<ObjectType>& j)
{  
    ar(j.dump());
}

template <template <typename...> class ObjectType>
void load(auto& ar, nlohmann::basic_json<ObjectType>& j)
{  
    std::string s;
    ar(s);
    j = nlohmann::basic_json<ObjectType>::parse(s);
}

通过这种方式,我们明确指定了如何序列化和反序列化 nlohmann::json 对象,避免了与 Waypoint 内部 serialize 函数的冲突。

总结

在处理同时支持 cerealnlohmann-json 的序列化时,遇到的冲突通常是由于重载函数之间的不明确匹配导致的。通过模板偏特化,解决这个问题,并确保两者兼容共存。

2025-11-18 13:38:07

评论