对模板化类链使用可变参数模板来生成序列化
Using variadic templates for chains of templated classes to generate serialization
>我有一个热切的项目,在这个项目中,我试图通过编写如下内容来尽可能轻松地启用结构的序列化:
class Data {
const QString& string();
void setString(QString string);
...
};
const QString stringName() { return "string"; }
template class <class Invokee, typename ContentType, const QString(*NameFunction)(), const ContentType& (Invokee::* Getter)() const> Field;
void serialize() {
Data data{...};
QJsonObject serialized
= serialize<Data, Field1, Field2, ...>;
}
它应该输出一个 JSON 对象。我最近发现 c++ 中有可变参数模板,并且非常兴奋地看到我是否可以定义这样一个序列化程序模板,该模板采用任意数量的字段,然后对其进行序列化。但是我卡在以下代码上:
template<
class Invokee,
typename ContentType,
const QString(*NameFunction)(),
const ContentType& (Invokee::* Getter)() const
>
void serializeToObject(QJsonObject& object, const Invokee& invokee) {
auto name = NameFunction();
object[name] = (invokee.*Getter)();
}
template<
class Invokee,
template<
class,
typename ContentType,
const QString(*)(),
const ContentType& (Invokee::* Getter)() const
> class Field,
class FieldClass,
class FieldInvokee,
typename FieldContentType,
const QString(*FieldNameFunction)(),
const FieldContentType& (Invokee::* FieldGetter)() const,
class... Args
>
void serializeToObject(QJsonObject& object, const Invokee& invokee) {
serializeToObject<FieldInvokee, FieldContentType, FieldNameFunction, FieldGetter>(object, invokee);
serializeToObject<Invokee, Args...>(object, invokee);
}
这似乎可以编译,但我还没有能够让它在实践中工作。也就是说,我正在尝试像这样使用它:
void tryOut() {
Data data;
data.setString("testString");
QJsonObject object{};
serializeToObject
<
Data,
Field<Data, QString, stringName, &Data::string>
>
(object, testClass);
}
编译器抱怨我对字符串名称的调用格式不正确。尽管 Field<...> 的测试实例化似乎有效,但对该函数的调用不会显示错误代码:
candidate template ignored: couldn't infer template argument 'NameFunction'
void serializeToObject(QJsonObject& object, Invokee& invokee) {
我挠头,不知道我做错了什么,或者这是否可能。
这是可能的,但正确的工具不是模板模板。要深入研究类型参数,就像你想通过提取Field
的所有模板参数一样,你需要使用部分模板专用化。
由于这一切都可以在 C++17 中简化一点,因此我将其一分为二:
C++11
解决方案首先,简化Field
使其成为常规模板:
template <
class Invokee,
typename ContentType,
const QString(*NameFunction)(),
const ContentType& (Invokee::* Getter)() const>
struct Field;
函数模板不支持部分模板专用化,因此下一步是创建虚拟结构。您实际上可以从字段中推断出我们需要的所有内容,因此字段是唯一必要的类型参数:
template <typename... Fields>
struct ObjectSerializer;
现在,它变得有趣了。将Field
的每个参数转换为参数包,并展开它们以获得专用类型:
template <
typename Invokee,
typename... ContentType,
const QString(*...NameFunction)(),
const ContentType& (Invokee::*...Getter)() const>
struct ObjectSerializer<Field<Invokee, ContentType, NameFunction, Getter>...>
{ /* ... */ }
在此怪物模板的正文中,使用调用运算符定义实际函数。此函数的主体应将提取到字段的值设置为object
的属性。
由于您实际上无法将参数包扩展为语句,因此您可以使用技巧。我将使用此处的技巧将语句隐藏在std::initializer_list
中,以便除赋值之外的所有内容都是常数折叠的:
constexpr void operator ()(QJsonObject& object, const Invokee& invokee) {
void(std::initializer_list<nullptr_t> {
(void(object[NameFunction()] = (invokee.*Getter)()), nullptr)...
});
}
然后你可以把整个东西包装在一个方便的函数中来隐藏结构。我从你的重新排列了一点,所以Invokee
从论点中推断出来:
template <typename... Fields, typename Invokee>
void serializeToObject(QJsonObject& object, const Invokee& invokee) {
ObjectSerializer<Fields...>{}(object, invokee);
}
之后,tryItOut(( 将像你期望的那样工作:
serializeToObject<
Field<Data, QString, stringName, &Data::string>
>(object, data);
演示:https://godbolt.org/z/kHTmPE
简化的 C++17 解决方案
如果您可以使用 C++17,您实际上可以通过使用自动非类型模板扣除来使其更好一点。对于字段,使用auto
代替 getter,并删除详细信息:
template <const QString(*NameFunction)(), auto Getter>
class Field;
但是当你部分专业化时,你仍然可以推断出所有这些信息。您还可以使用折叠表达式来简化"展开分配"技巧:
template <
typename Invokee,
typename... ContentType,
const QString(*...NameFunction)(),
const ContentType& (Invokee::*...Getter)() const>
struct ObjectSerializer<Field<NameFunction, Getter>...> {
template <typename TInvokee = Invokee>
constexpr void operator ()(QJsonObject& object, const Invokee& invokee) {
(void(object[NameFunction()] = (invokee.*Getter)()), ...);
}
};
所以现在,serializeToObject
每个字段只需要两个模板参数,而不是 4 个:
serializeToObject<
Field<stringName, &Data::string>
>(object, data);
演示:https://godbolt.org/z/UDinyi
作品在叮当中找到。但是哎哟,这会导致 gcc 爆炸(错误 92969(:
during RTL pass: expand
<source>: In function 'void serializeToObject(QJsonObject&, const Invokee&) [with Fields = {Field<stringName, &Data::string>}; Invokee = Data]':
<source>:34:34: internal compiler error: Segmentation fault
34 | ObjectSerializer<Fields...>{}(object, invokee);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
Please submit a full bug report,
(我会很快发送完整的错误报告(
简化的 C++17 解决方案(使用 gcc 解决方法(
这个 gcc 错误很糟糕,但可以通过使用不同的类型来序列化每个字段来解决:
template <typename Field>
struct FieldSerializer;
template <typename Invokee, typename ContentType, const QString(*NameFunction)(), const ContentType& (Invokee::*Getter)() const>
struct FieldSerializer<Field<NameFunction, Getter>>{
void operator()(QJsonObject& object, const Invokee& invokee) {
object[NameFunction()] = (invokee.*Getter)();
}
};
template <typename... Fields, typename Invokee>
void serializeToObject(QJsonObject& object, const Invokee& invokee) {
(void(FieldSerializer<Fields>{}(object, invokee)), ...);
}
这生成的类型比你想要的要多,但不像递归解决方案那么多。
演示:https://godbolt.org/z/kMYBAy
编辑:我已经修改了这个答案几次,首先是添加C++17简化,后来切换到非递归解决方案,希望有更好的编译时间。
- 如何在C++中序列化结构数据
- 序列化,没有库的整数,得到奇怪的结果
- C++转换参数初始化问题
- 如何知道QDataStream不能反序列化某些内容
- 如何使用Python从C++中读取谷物序列化数据
- 如何使用boost::具有嵌套结构和最小代码更改的序列化
- 带有Protobuf序列化的C++Hazelcast:字符串不是UTF-8格式的
- 自定义对象的dlib序列化在gcc中失败
- C++boost序列化多态性问题
- 增强基于 XML class_id的反序列化
- 提升反序列化对象具有 nan 或 -nan 值
- 在 cpp 中的平面缓冲区中序列化对象
- 每次进行继承时都需要提升::序列化::base_object吗?
- 对模板化类链使用可变参数模板来生成序列化
- C++ Boost - 序列化错误 - 将"const B"作为"this"参数
- 在对象序列化期间添加额外参数是否有更好的方法?
- 谷物/C++ 11 - 如何指定反序列化的可选参数
- 模板参数包序列化
- 如何在可视化工作室中自动更改函数的参数序列
- C++ Boost::序列化"no matching function for call"到我的加载函数参数中类的构造函数