如何用magic_enum配合cereal序列化枚举?反序列化报错求助
问题:magic_enum配合cereal实现枚举反序列化失败
序列化功能正常,但反序列化时触发错误,调用栈显示cereal尝试将JSON中的字符串值按int类型解析。
问题代码与现象
序列化代码
template <class Archive, cereal::traits::EnableIf<cereal::traits::is_text_archive<Archive>::value> = cereal::traits::sfinae, class T> std::enable_if_t<std::is_enum_v<T>, std::string> save_minimal( Archive &, const T& h ) { return std::string(magic_enum::enum_name(h)); } template <class Archive, cereal::traits::EnableIf<cereal::traits::is_text_archive<Archive>::value> = cereal::traits::sfinae, class T> std::enable_if_t<std::is_enum_v<T>, void> load_minimal( Archive const &, T& enumType, std::string const& str) { enumType = magic_enum::enum_cast<T>(str).value(); } enum class Color {RED, BLUE, YELLOW}; int main() { Color c = Color::RED; std::ofstream os("c.json"); // 修正原代码笔误 cereal::JSONOutputArchive outAr(os); outAr(CEREAL_NVP(c)); }
序列化输出的JSON
{ "c": "RED" }
反序列化代码
int main() { Color c; std::ifstream is("c.json"); cereal::JSONInputArchive in(is); in(CEREAL_NVP(c)); }
错误调用栈
cerealtest.exe!rapidjson::GenericValue<rapidjson::UTF8<char>,rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>>::GetInt() Line 1744 C++ cerealtest.exe!cereal::JSONInputArchive::loadValue<int,0>(int & val) Line 700 C++ cerealtest.exe!cereal::load<int,0>(cereal::JSONInputArchive & ar, int & t) Line 1115 C++ cerealtest.exe!cereal::InputArchive<cereal::JSONInputArchive,0>::processImpl<int,0>(int & t) Line 942 C++ cerealtest.exe!cereal::InputArchive<cereal::JSONInputArchive,0>::process<int &>(int & head) Line 854 C++ cerealtest.exe!cereal::InputArchive<cereal::JSONInputArchive,0>::processImpl<enum Color,0>(Color & t) Line 963 C++
错误原因
- ADL查找失败:cereal依赖参数依赖查找(ADL)定位序列化函数,但自定义的
save_minimal和load_minimal不在cereal或枚举类型的命名空间中,导致cereal无法找到重载,转而使用默认的枚举序列化逻辑(将枚举当作整数处理)。 - 模板匹配优先级问题:原模板的SFINAE条件组合方式可能未正确触发,导致cereal未选择我们定义的
load_minimal重载。
修复方案
方案1:将模板函数放入cereal命名空间
让ADL能正确识别自定义序列化函数:
#include <cereal/cereal.hpp> #include <cereal/archives/json.hpp> #include <fstream> #include <magic_enum.hpp> namespace cereal { template <class Archive, class T> std::enable_if_t<std::is_enum_v<T> && traits::is_text_archive<Archive>::value, std::string> save_minimal(Archive &, const T& h) { return std::string(magic_enum::enum_name(h)); } template <class Archive, class T> std::enable_if_t<std::is_enum_v<T> && traits::is_text_archive<Archive>::value, void> load_minimal(Archive const &, T& enumType, const std::string& str) { auto result = magic_enum::enum_cast<T>(str); if (!result.has_value()) { throw std::invalid_argument("Invalid enum string: " + str); } enumType = result.value(); } } // namespace cereal enum class Color {RED, BLUE, YELLOW}; // 序列化示例 int main() { Color c = Color::RED; std::ofstream os("c.json"); cereal::JSONOutputArchive outAr(os); outAr(CEREAL_NVP(c)); return 0; } // 反序列化示例 // int main() // { // Color c; // std::ifstream is("c.json"); // cereal::JSONInputArchive in(is); // in(CEREAL_NVP(c)); // return 0; // }
方案2:为特定枚举显式实现序列化
如果仅需支持特定枚举,可避免模板匹配问题:
#include <cereal/cereal.hpp> #include <cereal/archives/json.hpp> #include <fstream> #include <magic_enum.hpp> enum class Color {RED, BLUE, YELLOW}; namespace cereal { template <class Archive> std::enable_if_t<traits::is_text_archive<Archive>::value, std::string> save_minimal(Archive &, const Color& h) { return std::string(magic_enum::enum_name(h)); } template <class Archive> std::enable_if_t<traits::is_text_archive<Archive>::value, void> load_minimal(Archive const &, Color& enumType, const std::string& str) { auto result = magic_enum::enum_cast<Color>(str); if (!result.has_value()) { throw std::invalid_argument("Invalid Color string: " + str); } enumType = result.value(); } } // namespace cereal // 序列化/反序列化代码同上
额外注意事项
- 原序列化代码存在笔误:
std::ifstream is("c.json");应改为std::ofstream os("c.json");,否则无法写入文件。 - 避免直接调用
magic_enum::enum_cast<T>(str).value(),当字符串无法匹配枚举值时会抛出std::bad_optional_access异常,建议先通过has_value()判断有效性再处理。
内容的提问来源于stack exchange,提问作者yuziqi
相关产品推荐
相关产品推荐

