Boost.Serialize编写通用类map容器序列化函数的重载歧义问题
问题描述
Boost.Serialize 为 std::map / std::multimap 提供了显式序列化能力,但该实现无法适配其他类map容器。
我希望实现通用的类map容器序列化逻辑,避免重复编写代码,但编译时Boost报重载歧义错误。
现有实现代码
/** Some sfinae to detect types that behave like std::map **/ template<typename T, typename = void> struct is_map_ish : std::false_type { }; template<template<typename K, typename V> typename Map, typename K, typename V> struct is_map_ish< Map<K,V>, std::void_t< typename Map<K,V>::key_type, typename Map<K,V>::value_type > > : std::true_type { }; template<class Archive, class Map> inline auto save( Archive & ar, const Map& t, const unsigned int /* file_version */, std::enable_if_t<is_map_ish<Map>::value>* = 0 ) -> std::enable_if_t<is_map_ish<Map>::value> { boost_155::serialization::stl::save_collection< Archive, Map >(ar, t); } template<class Archive, class Map> inline auto load( Archive & ar, Map& t, const unsigned int /* file_version */, std::enable_if_t<is_map_ish<Map>::value>* = 0 ) -> std::enable_if_t<is_map_ish<Map>::value> { boost_155::serialization::stl::load_collection< Archive, Map, boost_155::serialization::stl::archive_input_map< Archive, Map >, boost_155::serialization::stl::no_reserve_imp<Map> >(ar, t); } // split non-intrusive serialization function member into separate // non intrusive save/load member functions template<class Archive, class Map> inline auto serialize( Archive & ar, Map &t, const unsigned int file_version, std::enable_if_t<is_map_ish<Map>::value>* = 0 ) -> std::enable_if_t<is_map_ish<Map>::value> { boost_155::serialization::split_free(ar, t, file_version); }
冲突原因
编写的serialize函数与Boost中如下默认实现产生了冲突:
// default implementation - call the member function "serialize" template<class Archive, class T> inline void serialize( Archive & ar, T & t, const BOOST_155_PFTO unsigned int file_version ){ access::serialize(ar, t, static_cast<unsigned int>(file_version)); }
该Boost默认实现无法修改,已尝试在返回值和函数参数列表中添加SFINAE约束,仍无法解决重载歧义问题。目前仅能使用C17标准,不能使用C20的concept特性。
解决方案
产生歧义的核心原因是你为自定义serialize函数添加了第四个带默认值的参数,调用时传入三个参数会同时匹配Boost的三参数默认实现和你的四参数(带默认值)实现,二者优先级相同导致歧义。另外原有is_map_ish判断逻辑宽松、模板匹配范围不足,也会影响匹配优先级。
可以按如下步骤修改:
- 优化
is_map_ish的匹配规则,同时兼容带默认模板参数的map类容器(比如std::map自带比较器、分配器两个默认参数),增加mapped_type等map专属特征判断,避免误匹配:
template<typename T, typename = void> struct is_map_ish : std::false_type { }; template<template<typename...> typename Map, typename K, typename V, typename... Rest> struct is_map_ish< Map<K,V,Rest...>, std::void_t< typename Map<K,V,Rest...>::key_type, typename Map<K,V,Rest...>::mapped_type, typename Map<K,V,Rest...>::value_type > > : std::true_type {};
- 将SFINAE约束从函数默认参数移到模板参数列表,作为非类型模板参数的默认值,同时保持serialize/save/load都是三参数签名,和Boost默认实现的参数数量一致:
template<class Archive, class Map, std::enable_if_t<is_map_ish<Map>::value, int> = 0> inline void save( Archive & ar, const Map& t, const unsigned int /* file_version */ ) { boost_155::serialization::stl::save_collection<Archive, Map>(ar, t); } template<class Archive, class Map, std::enable_if_t<is_map_ish<Map>::value, int> = 0> inline void load( Archive & ar, Map& t, const unsigned int /* file_version */ ) { boost_155::serialization::stl::load_collection< Archive, Map, boost_155::serialization::stl::archive_input_map<Archive, Map>, boost_155::serialization::stl::no_reserve_imp<Map> >(ar, t); } template<class Archive, class Map, std::enable_if_t<is_map_ish<Map>::value, int> = 0> inline void serialize( Archive & ar, Map &t, const unsigned int file_version ) { boost_155::serialization::split_free(ar, t, file_version); }
- 将以上实现放在
boost::serialization命名空间内,遵循Boost.Serialization的重载查找规则,进一步确保自定义重载优先级高于默认实现。
修改后当传入的容器满足is_map_ish约束时,你的实现比Boost无约束的默认模板更特化,编译器会优先选择你的实现,不会再出现重载歧义。
内容的提问来源于stack exchange,提问作者Jean-Michaël Celerier
相关产品推荐
相关产品推荐

