使用模板检查结构中的字段,启用函数,如果失败则给出错误消息?

Using templates to check for fields in a struct, enable a function if, and give a nice error message if that fails?

我有一个 C++ class 定义了数据序列化的接口 JSON:

 class JsonSerializable
 {
 public:
      QJsonDocument toJSON() const;
      void fromJSON(QJsonDocument const& json);

 protected:
     virtual QJsonDocument serialize() const = 0;
     virtual oid deserialize(QJsonDocument const& json) = 0;
 };

toJSON()serialize() 及其对应物的想法是 class 需要实现的受保护方法只需要关心实际处理数据,而public 方法执行基本的有效性检查。

现在我想介绍 JSON 用于更细粒度检查的架构。我只想在模式存在时启用 JSON 模式部分。因此,我开始像这样介绍 struct

 template <class Serializable>
 struct JsonSchema {};

 template <>
 struct JsonSchema<MySerializingClass>
 {
      static const char schemaURI[] = "...";
 };

我还没有完成这个方法,因为我不确定如何实现以下内容:

  1. 仅当存在 JsonSchema 结构的模板特化时,才应编译 fromJSON() 中的模式检查代码。
  2. 如果这个结构存在,我希望编译器检查 schemaURI 字段是否存在以及它的大小是否 >0。如果没有,我想给程序员一个描述性的错误消息,就像我可以用 BOOST_STATIC_ASSERT.

这可能吗? API 未定案;这里最重要的部分是检查应该在编译时进行。 schemaURI 参数由程序员设置,并且在运行时不会更改 --- 模式文件是分布式程序的一部分并通过资源系统进行管理,因此如果程序编译则保证可用。

我相信您目前的设计无法满足您的要求。例如,fromJSON 的示例代码不可能静态地知道调用它的 class 的类型。既然你想要编译类型检查,我想你可能不得不使用静态多态性,像这样:

template<typename T>
struct JsonSchema {};

template<typename Derived, typename SchemaCheck = void>
class JsonSerializable {
public:
    QJsonDocument toJSON() const;
    void fromJSON(QJsonDocument const& json);
};

template<typename Derived, typename SchemaCheck>
QJsonDocument JsonSerializable<Derived,SchemaCheck>::toJSON() const {
    // do whatever you need to do here, and call serialize() like this:
    static_cast<Derived const*>(this)->serialize();
}

template<typename Derived, typename SchemaCheck>
void JsonSerializable<Derived,SchemaCheck>::fromJSON(QJsonDocument const& json) {
    // do whatever you need to do when there's no schema, and call deserialize() like this:
    static_cast<Derived*>(this)->deserialize(json);
}

template<typename Derived>
class JsonSerializable<Derived,
    // We check extent > 1 instead of extent > 0 because a string constant of length 0 requires an array of length 1 to hold it.
    // If schemaURI doesn't exist at all, this will also fail and cause the default version (above) to be used.
    std::enable_if<(std::extent<decltype(JsonSchema<Derived>::schemaURI)>::value > 1), Derived>> {
public:
    // It's probably easier to define these inline. It might get pretty complicated otherwise.
    QJsonDocument toJSON() const {
        // Pretty much the same as before, unless you need some different behaviour when there's a schema
        static_cast<Derived const*>(this)->serialize();
    }
    void fromJSON(QJsonDocument const& json) {
        // There's a schema now, so act accordingly.
        static_cast<Derived*>(this)->deserialize(json);
    }
};

然后你像这样声明一个可序列化的class:

class MySerializingClass : public JsonSerializable<MySerializingClass> {
public:
    QJsonDocument serialize() const;
    void deserialize(QJsonDocument const& json);
};

如果您需要能够在不知道它们是什么的情况下传递 JsonSerializable 个对象,您可以通过引入一个额外的基础 class:

来做到这一点
class JsonSerializableBase {
public:
    virtual QJsonDocument toJSON() const = 0;
    virtual void fromJSON(QJsonDocument const& json) = 0;
};

然后更改 JsonSerializable 声明以继承它:

template<typename Derived, typename SchemaCheck = void>
class JsonSerializable : public JsonSerializableBase

对于架构案例,它看起来像这样:

template<typename Derived>
class JsonSerializable<Derived,
    // We check extent > 1 instead of extent > 0 because a string constant of length 0 requires an array of length 1 to hold it.
    std::enable_if<(std::extent<decltype(JsonSchema<Derived>::schemaURI)>::value > 1), Derived>> : public JsonSerializableBase

我测试过此代码可以使用以下内容编译 main():

using QJsonDocument = std::string; // Since I don't have Qt handy
int main(int,char*[]) {
    MySerializingClass foo;
    foo.toJSON();
    foo.fromJSON("abc");
    return 0;
}

希望它能如您所愿。

万一有人想知道如果静态多态性不是一个选项时如何做到这一点,例如,当 Qt 的 moc 干扰时:一个自由函数可以提供帮助。这是代码:

class JsonSerializable
{
public:
    virtual QJsonDocument toJSON() const = 0;
    virtual void fromJSON(const QJsonDocument &json) = 0;
};


template <class C>
struct JsonSchema
{
    static constexpr char const schemaURI[] = "";
};


template <
    class C,
    typename std::enable_if<(std::is_base_of<JsonSerializable, C>
            ::value
        && std::extent<decltype(JsonSchema<C>::schemaURI)>
             ::value <= 1),
        int>::type = 0>
void deserialize(C& serializable, QJsonDocument const& json)
{
    serializable.fromJSON(json);
}


template <
    class C,
    typename std::enable_if<(std::is_base_of<JsonSerializable, C>
            ::value
        && std::extent<decltype(JsonSchema<C>::schemaURI)>
             ::value > 1),
        int>::type = 0>
void deserialize(C& serializable, QJsonDocument const& json)
{
    /* Do the schema checking here: */

    checkSchema(json, JsonSerializable<C>::schemaURI);
    serializable.fromJSON(json);
}