如何通过结构体定义正确运用C++20 Concept/SFINAE实现函数选择?
问题
我正在尝试使用RapidJSON开发可序列化变量包装器,需支持用户自定义转换函数或默认字面量转换函数,同时实现MSVS与MinGW跨编译方案。当UserType为非字面量类型时,编译期选择用户定义的toJson/fromJson;为字面量类型时,使用rapidjson::Value的构造函数与Get<LiteralType>方法。目前字面量类型可正常工作,但为自定义UserType应用SFINAE或Concept时失败:通过SFINAE定义转换器结构体时,触发RapidJSON内部模板错误,提示'Get'不是rapidjson::internal::TypeHelper<rapidjson::GenericValue<rapidjson::UTF8<> >, CustomType1>的成员。
以下是复现问题的最小示例代码:
RapidJSON类型系统模拟代码
namespace rapidjson { template <class T = int> class UTF8 { }; namespace internal { template <typename ValueType, typename T> struct TypeHelper {}; template<typename ValueType> struct TypeHelper<ValueType, double> { static double Get(const ValueType& v) { return v.GetDouble(); } }; template<typename ValueType> struct TypeHelper<ValueType, int> { static double Get(const ValueType& v) { return v.GetInt(); } }; } template <typename BaseAllocator = void> class MemoryPoolAllocator { }; template <typename Encoding, typename Allocator = MemoryPoolAllocator<> > class GenericValue { typedef GenericValue<Encoding, Allocator> ValueType; public: explicit GenericValue() = default; explicit GenericValue(double d) { } explicit GenericValue(int i) { } template <typename T> T Get() const { return internal::TypeHelper<ValueType, T>::Get(*this); } double GetDouble() const { return 333.3; } int GetInt() const { return 333; } }; //! GenericValue with UTF8 encoding typedef GenericValue<UTF8<> > Value; }
我的包装器代码
namespace Serializable { // Base template for literal types, no constrains // Assume that user for his type will define the same as the static function at UserJsonConversions class template<class UserType> void fromJson(UserType& userValue, const rapidjson::Value& initializer) { userValue = initializer.template Get<UserType>(); } template <class UserType> using FromJsonFuncPtrType = void(*)(UserType&, const rapidjson::Value&); // As I know, if while checking statement for validity the error will be thrown, // the concept must fails and belongs to it declaration expression must be skipped by SFINAE template <class UserType> concept HasFromJsonCast = requires { rapidjson::Value().Get<UserType>(); }; template <class UserType> concept NoFromJsonCast = !HasFromJsonCast<UserType>; // SFINAE helper, // In the base template suppose we received a custom user type template <class UserType, class ExternalConversions, class = void> struct Distinct { inline static constexpr const FromJsonFuncPtrType<UserType> fromJson = &ExternalConversions::fromJson; }; template <class UserType, class ExternalConversions> struct Distinct<UserType, ExternalConversions, std::void_t<std::enable_if<HasFromJsonCast<UserType>, void>>> { inline static constexpr const FromJsonFuncPtrType<UserType> fromJson = &fromJson; }; }
用户程序代码
// User-related types and conversions struct CustomType1 { }; struct CustomType2 { }; struct UserJsonConversions { static rapidjson::Value fromJson(CustomType1& customValue, const rapidjson::Value& initializer) { return rapidjson::Value(); } static rapidjson::Value fromJson(CustomType2& customValue, const rapidjson::Value& initializer) { return rapidjson::Value(); } }; template<class UserType> inline auto attempt() { rapidjson::Value json; UserType customValue; Serializable::Distinct<UserType, UserJsonConversions>::fromJson(customValue, json); } int main() { // attempt<double>(); attempt<CustomType1>(); }
请问该如何正确使用SFINAE解决此问题?
解决方案
核心问题有两个:一是原HasFromJsonCast概念的检查逻辑会触发RapidJSON内部的硬错误(而非SFINAE友好的推导失败);二是用户自定义转换函数的签名和包装器期望的函数指针不匹配。
修复步骤
- 重构类型检查逻辑:避免直接调用
Get(),改用SFINAE友好的方式检查rapidjson::internal::TypeHelper是否为当前类型特化了Get成员。 - 统一函数签名:调整用户自定义
fromJson的返回值为void,匹配包装器的函数指针类型。
修复后的完整代码示例
调整后的包装器代码
#include <type_traits> namespace Serializable { // 基础字面量类型转换函数 template<class UserType> void fromJson(UserType& userValue, const rapidjson::Value& initializer) { userValue = initializer.template Get<UserType>(); } template <class UserType> using FromJsonFuncPtrType = void(*)(UserType&, const rapidjson::Value&); // SFINAE友好的检查:判断TypeHelper是否有Get成员 template<typename ValueType, typename T> struct has_type_helper_get { // 重载测试:如果TypeHelper有Get成员,匹配此版本 template<typename V, typename U> static auto test(int) -> decltype(V::Get(std::declval<const V&>()), std::true_type{}); // 兜底版本:如果上面的测试失败,匹配此版本 template<typename, typename> static auto test(...) -> std::false_type; static constexpr bool value = decltype(test<rapidjson::internal::TypeHelper<ValueType, T>, T>(0))::value; }; // 基于SFINAE检查结果定义Concept template <class UserType> concept HasFromJsonCast = has_type_helper_get<rapidjson::Value, UserType>::value; // SFINAE特化:默认匹配自定义类型 template <class UserType, class ExternalConversions, class = void> struct Distinct { inline static constexpr FromJsonFuncPtrType<UserType> fromJson = [](UserType& val, const rapidjson::Value& json) { ExternalConversions::fromJson(val, json); }; }; // SFINAE特化:匹配字面量类型 template <class UserType, class ExternalConversions> struct Distinct<UserType, ExternalConversions, std::enable_if_t<HasFromJsonCast<UserType>>> { inline static constexpr FromJsonFuncPtrType<UserType> fromJson = &fromJson; }; }
修正后的用户转换函数代码
// User-related types and conversions struct CustomType1 { }; struct CustomType2 { }; struct UserJsonConversions { // 修正返回值为void,匹配包装器的函数指针类型 static void fromJson(CustomType1& customValue, const rapidjson::Value& initializer) { // 这里实现自定义转换逻辑 } static void fromJson(CustomType2& customValue, const rapidjson::Value& initializer) { // 这里实现自定义转换逻辑 } }; template<class UserType> inline auto attempt() { rapidjson::Value json; UserType customValue; Serializable::Distinct<UserType, UserJsonConversions>::fromJson(customValue, json); } int main() { attempt<double>(); // 字面量类型正常工作 attempt<CustomType1>(); // 自定义类型正常工作 }
关键说明
- SFINAE友好检查:通过
has_type_helper_get的重载测试,避免直接触发TypeHelper::Get的实例化错误,而是利用模板推导失败来判断类型是否被RapidJSON支持,符合SFINAE的要求。 - 签名统一:用户自定义的
fromJson必须和包装器的函数指针类型一致,否则无法赋值给FromJsonFuncPtrType变量。 - Concept优化:如果使用C++20及以上,也可以直接用Concept约束
Distinct的特化,代码会更简洁,但核心的类型检查逻辑不变。
内容的提问来源于stack exchange,提问作者Aeomanate
相关产品推荐
相关产品推荐

