将可变参数模板用于模板化类链以生成序列化

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

我有一个热切的项目,在该项目中,我尝试通过按照以下方式写一些东西来尽可能轻松地实现结构的序列化:

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 ++中有可变参数模板,很高兴看到我是否可以定义这样的Serializer模板,该模板接受任意数量的Field,然后对其进行序列化。但是我被困在下面的代码:

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);
}

编译器抱怨我对stringName的调用格式错误。尽管Field <...>的测试实例化似乎起作用,但是对该函数的调用没有错误代码:

candidate template ignored: couldn't infer template argument 'NameFunction'
void serializeToObject(QJsonObject& object, Invokee& invokee) {

[我正在为自己做错的事情或是否完全有可能而挠头。

c++ qt templates variadic-templates template-templates
1个回答
1
投票

这是可能的,但是正确的工具不是模板模板。要深入研究类型参数,就像您要提取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>...>
{ /* ... */ }

在此monstrosity模板的主体中,使用调用运算符定义实际功能。该函数的主体应将object的属性设置为提取到字段的值。

由于您实际上无法将参数包扩展为语句,因此请使用技巧。我将使用here中的技巧来将语句隐藏在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代替吸气剂,并删除详细信息:

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

可以在c中找到。但是,这会导致gcc爆炸:

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,

((我will不久将发送完整的错误报告)

简化的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的简化,然后切换到希望具有更好的编译时间的非递归解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.