使用模板检查结构中的字段,启用函数,如果失败则给出错误消息?
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[] = "...";
};
我还没有完成这个方法,因为我不确定如何实现以下内容:
- 仅当存在
JsonSchema
结构的模板特化时,才应编译 fromJSON()
中的模式检查代码。
- 如果这个结构存在,我希望编译器检查
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);
}
我有一个 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[] = "...";
};
我还没有完成这个方法,因为我不确定如何实现以下内容:
- 仅当存在
JsonSchema
结构的模板特化时,才应编译fromJSON()
中的模式检查代码。 - 如果这个结构存在,我希望编译器检查
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);
}