You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过结构体定义正确运用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友好的推导失败);二是用户自定义转换函数的签名和包装器期望的函数指针不匹配。

修复步骤

  1. 重构类型检查逻辑:避免直接调用Get(),改用SFINAE友好的方式检查rapidjson::internal::TypeHelper是否为当前类型特化了Get成员。
  2. 统一函数签名:调整用户自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 15:44:55