如何正确编写和应用c++20概念/SFINAE/两者通过结构定义选择函数?

问题描述 投票:0回答:1

请帮助我了解我做错了什么。

我正在尝试使用 RapidJSON 制作一个可序列化的变量包装器。我想提供用户定义的转换函数或使用默认的文字转换函数。另外,我想写一个交叉编译的解决方案(MSVS & MinGW)。

当 UserType 不是文字类型时,在编译时选择用户定义的

toJson / fromJson
,否则使用定义的构造函数
rapidjson::Value ()
rapidjson::Value ().template Get <LiteralType>
函数。


在尝试将 SFINAE 或概念应用于自定义用户类型时,我遇到了困难。但对于文字类型(int、double 等)它工作得很好。

问题:尝试通过 SFINAE 定义带有转换器的结构由 UserType 解析失败,因为我想通过 SFINAE 忽略内部 RapidJSON 模板错误:

error: 'Get' is not a member of 'rapidjson::internal::TypeHelper<rapidjson::GenericValue<rapidjson::UTF8<> >, CustomType1>'
   32 |         T Get() const { return internal::TypeHelper<ValueType, T>::Get(*this); }
      |                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~

为最小示例重新创建的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?

c++ c++20 sfinae c++-concepts detection-idiom
1个回答
0
投票

所以我解决了。首先,我改变了主要概念

    template <class UserType>
    concept HasFromJsonCast = requires {
        rapidjson::Value().Get<UserType>();
    };

    template <class UserType, class ExternalConverters>
    concept HasFromJsonUserCast = requires (UserType& userValue) {
        { ExternalConverters::fromJson(userValue, rapidjson::Value()) } -> std::convertible_to<void>;
    };

辅助类 Distinct 看起来像这样

    template <class UserType, class ExternalJsonConverters>
    class Distinct {
        using FromJsonFuncPtrType = void(*)(UserType&, const rapidjson::Value&);
        using ToJsonFuncPtrType = rapidjson::Value(*)(const UserType&);

        static constexpr auto GetFromJsonConverter() -> FromJsonFuncPtrType
        {
            if constexpr(HasFromJsonUserCast<UserType, ExternalJsonConverters>)
            { return &ExternalJsonConverters::fromJson; }
            else
            { return &fromJsonInternal<UserType>; };
        }
        static constexpr auto GetToJsonConverter() -> ToJsonFuncPtrType
        {
            if constexpr(HasToJsonUserCast<UserType, ExternalJsonConverters>)
            { return &ExternalJsonConverters::toJson; }
            else
            { return &toJsonInternal<UserType>; };
        }
    public:
        inline static constexpr const FromJsonFuncPtrType fromJson = GetFromJsonConverter();
        inline static constexpr const ToJsonFuncPtrType toJson = GetToJsonConverter();
    };

因此调用者可以自由地以一致的方式序列化他的类型和内部类型

template <class UserType>
inline auto attempt()
{
    rapidjson::Value json;
    UserType val;

    Converters::Distinct<UserType, UserJsonConversions>::fromJson(val, json);
    Converters::Distinct<UserType, UserJsonConversions>::toJson(val);
}

inline void f()
{
    attempt<sf::Uint32>();
    attempt<sf::VideoMode>();
}
© www.soinside.com 2019 - 2024. All rights reserved.