C++ 对模板化类链使用可变模板生成序列化
我有一个急切的项目,在这个项目中,我试图通过编写以下内容尽可能轻松地实现结构的序列化:C++ 对模板化类链使用可变模板生成序列化,c++,qt,templates,variadic-templates,template-templates,C++,Qt,Templates,Variadic Templates,Template Templates,我有一个急切的项目,在这个项目中,我试图通过编写以下内容尽可能轻松地实现结构的序列化: class Data { const QString& string(); void setString(QString string); ... }; const QString stringName() { return "string"; } template class <class Invokee, typename ContentType, const QString
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, ...>;
}
我在琢磨我做错了什么,或者这是否可能。这是可能的,但正确的工具不是模板。要深入挖掘类型参数,就像您希望提取
字段
的所有模板参数一样,您需要使用部分模板专门化
由于在C++17中,这一切都可以简化一点,因此我将把它分为两部分:
C++11解决方案
首先,简化字段
,使其成为常规模板:
模板<
类被调用者,
typename ContentType,
常量QString(*NameFunction)(),
const ContentType&(被调用方::*Getter)()const>
结构域;
函数模板不支持部分模板专门化,因此下一步是生成虚拟结构。实际上,您可以从字段中推断出我们需要的所有内容,因此字段是唯一必要的类型参数:
模板
结构对象序列化程序;
现在,它变得有趣了。将字段的每个参数
转换为一个参数包,并展开它们以获得专用类型:
模板<
类型名被调用者,
类型名。。。内容类型,
常量QString(*…名称函数)(),
const ContentType&(被调用方::*…Getter)()const>
结构对象序列化程序
{ /* ... */ }
在这个怪物模板的主体中,使用call操作符来定义实际的函数。此函数的主体应将object
的属性设置为提取到字段的值
由于无法将参数包实际扩展为语句,因此必须使用技巧。我将使用来自的技巧将语句隐藏在std::initializer\u list
中,以这样一种方式,除了赋值之外的所有语句都是常量折叠的:
consteprvoid操作符()(QJsonObject&object,constinvokee&Invokee){
无效(标准::初始值设定项列表){
(void(object[NameFunction()]=(invokee.*Getter)(),nullptr)。。。
});
}
然后你可以用一个方便的函数来隐藏这个结构。我将它重新排列了一点,因此Invokee
是从参数中推导出来的:
template <typename... Fields, typename Invokee>
void serializeToObject(QJsonObject& object, const Invokee& invokee) {
ObjectSerializer<Fields...>{}(object, invokee);
}
但当你进行部分专业化时,你仍然可以推断出所有这些信息。您还可以使用折叠表达式简化“展开赋值”技巧:
模板<
类型名被调用者,
类型名。。。内容类型,
常量QString(*…名称函数)(),
const ContentType&(被调用方::*…Getter)()const>
结构对象序列化程序{
模板
constexpr void操作符()(QJsonObject和object,const Invokee和Invokee){
(void(object[NameFunction()]=(invokee.*Getter)(),…);
}
};
所以现在,serializeToObject
每个字段只需要两个模板参数,而不是4个:
serializeToObject<
领域
>(对象、数据);
演示:
作品在叮当声中找到。但是哎哟,这导致gcc爆炸():
RTL过程中的:展开
:在函数“void serializeToObject(QJsonObject&,const Invokee&)[带字段={Field};Invokee=Data]”中:
:34:34:内部编译器错误:分段错误
34 | ObjectSerializer{}(object,invokee);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
请提交完整的bug报告,
(我将很快发送完整的错误报告)
简化的C++17解决方案(使用gcc解决方案)
这个gcc错误很糟糕,但可以通过使用不同的类型序列化每个字段来解决:
模板
结构字段序列化器;
模板
结构字段序列化程序{
void操作符(){
对象[NameFunction()]=(invokee.*Getter)();
}
};
模板
void serializeToObject(QJsonObject和object、const Invokee和Invokee){
(void(FieldSerializer{}(object,invokee)),…);
}
这会生成比您希望的更多的类型,但不像递归解决方案那样多的类型
演示:
编辑:我对这个答案做了几次修改,首先添加了C++17简化,然后切换到一个非递归的解决方案,希望有更好的编译时间。[OT]:返回const对象是无用的(甚至更糟,因为它禁止一些优化)。您可能会对
BOOST\u HANA\u DEFINE\u STRUCT
或类似的解决方案感兴趣。(当您使用Qt时,可能会有所帮助)。为什么不在常量字段命名函数中使用现成的序列化库,例如?@Jarod42?你有这方面的文章吗?“我很想把它读出来。”弗拉基米尔贝索夫:嗯,额外的一个图书馆永远是一个需要跟踪的地方。但我可以看到如果这不是问题的话,人们会如何使用它。太棒了。我想给你买杯啤酒好的先生,这打开了很多其他的模版把戏,我一定要试试。我认为编译器可能会为此生成大量的中间结构。对,所有递归解决方案都涉及对编译有相当负面影响的TypeSploion。如果您可以只扩展参数包或使用折叠表达式,那就更好了,但我不认为您可以扩展部分专门化。但我会是猴子的叔叔:。。。叮当作响。这是我的错!所以woohoo,我开始提交一个bug报告。好的,我修改了解决方案,添加了一个非递归的解决方案,以及一个解决我发现的gcc bug的方法。
void tryOut() {
Data data;
data.setString("testString");
QJsonObject object{};
serializeToObject
<
Data,
Field<Data, QString, stringName, &Data::string>
>
(object, testClass);
}
candidate template ignored: couldn't infer template argument 'NameFunction'
void serializeToObject(QJsonObject& object, Invokee& invokee) {
template <typename... Fields, typename Invokee>
void serializeToObject(QJsonObject& object, const Invokee& invokee) {
ObjectSerializer<Fields...>{}(object, invokee);
}
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,