Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-66744

Allow constrained use of Q_GADGET in template classes

    XMLWordPrintable

Details

    • fee8944cbe2a976e1b4a71c5df048e84a0bc6db0 1b996c138f6afae7ccf9d9ec87036768fd8ef329

    Description

      Currently it is not possible to use Q_GADGET in template classes (because of moc limitations etc.).

      However it is perfectly possible to define a Q_GADGET enabled base class and inherit a template class (subsequently called GT) adding type bound (type safe) functionality. This is similar to the approach of using template functionality in Q_OBJECT based class hierarchies.

      The constraint with both is that all template class instances will share the metaobject of the base class, so you obviously cannot add new slots, invokables or properties and the metaobject classname is that of the base class - not a big deal in practice.

      The problem is that on the QML side the V4 engine won't wrap a QVariant of an GT instance correctly as a Q_GADGET (see ExecutionEngine::metaTypeToJS) because on registration the QMetaType::IsGadget flag is not set for the template classes. So a QVariant wrapping a GT instance cannot be used on the QML side (no property access etc).

      Reason for this is the trait magic applied in the background. Without Q_GADGET in the template class (which is obviously not allowed) it is not tagged with "void qt_check_for_QGADGET_macro()" and thus the flag is not set during registration.

      Putting this tag manually into the template class does the trick. Now the GT QVariant can be used nicely on the Qml side.

      Proposal: Add a Q_GADGET_INHERITED_TEMPLATE macro that only adds the tag to a template class.

      Please check the following complete example - if the Q_GADGET_INHERITED_TEMPLATE macro is being removed from the template class, property access and toString invocation on the Qml side will fail for the typed descriptor instances:

      #include <QGuiApplication>
      #include <QQmlApplicationEngine>
      #include <QQmlContext>
      #include <QDebug>
      
      // generic descriptor to address some object
      // this is the Q_GADGET enabled base class
      class Descriptor
      {
        Q_GADGET
        Q_PROPERTY(QString accountId MEMBER m_accountId)
        Q_PROPERTY(QString objectId MEMBER m_objectId)
      
      public:
      
        Descriptor(const char *tn, const QString &accountId, const QString &objectId) :
          m_tn(tn),
          m_accountId(accountId),
          m_objectId(objectId)
        {}
        Descriptor() = default;
      
        const char *tn() const { return m_tn; }
        QString accountId() const { return m_accountId; }
        QString objectId() const { return m_objectId; }
      
        Q_INVOKABLE QString toString() const
        {
          return "Descriptor (" + QString::fromLatin1(m_tn) + ", " + m_accountId + ", " + m_objectId + ")";
        }
      
      private:
      
        const char *m_tn = 0;
        QString m_accountId;
        QString m_objectId;
      };
      
      // fake the Q_GAGDET type level stuff so that
      // (QMetaType().flags() & QMetaType::IsGadget) == true
      // This will make QVariants of TypedDescriptor<T> work in Qml!
      // Note that the template instances share the meta object with Descriptor
      // so the classname info will be "Descriptor".
      // This depends on Qt compile time internals!
      #define Q_GADGET_INHERITED_TEMPLATE \
      public: \
          void qt_check_for_QGADGET_macro(); \
      private: \
      
      // this is the type safe descriptor
      template <typename T>
      class TypedDescriptor : public Descriptor
      {
        Q_GADGET_INHERITED_TEMPLATE
      
      public:
        TypedDescriptor(const QString &accountId, const QString &objectId) : Descriptor(typeid(T).name(), accountId, objectId) {}
        TypedDescriptor(const T &o) : Descriptor(o.getDescriptor()) {}
        TypedDescriptor(const TypedDescriptor &other) = default;
        TypedDescriptor() = default;
      };
      
      // descriptors bound to some c++ type so we have type safety on c++ side
      class TypeA;
      typedef TypedDescriptor<TypeA> TypeADescriptor;
      
      class TypeB;
      typedef TypedDescriptor<TypeB> TypeBDescriptor;
      
      // dummy types
      class TypeA
      {
      };
      
      class TypeB
      {
      };
      
      Q_DECLARE_METATYPE(Descriptor)
      Q_DECLARE_METATYPE(TypeADescriptor)
      Q_DECLARE_METATYPE(TypeBDescriptor)
      
      int main(int argc, char *argv[])
      {
        qRegisterMetaType<Descriptor>();
        qRegisterMetaType<TypeADescriptor>();
        qRegisterMetaType<TypeBDescriptor>();
      
        QGuiApplication app(argc, argv);
      
        QQmlApplicationEngine engine;
        QQmlContext *c = engine.rootContext();
        c->setContextProperty("descriptor", QVariant::fromValue(Descriptor("generic", "account1", "someID")));
        c->setContextProperty("descriptor_a", QVariant::fromValue(TypeADescriptor("account1", "id_for_type_a")));
        c->setContextProperty("descriptor_b", QVariant::fromValue(TypeBDescriptor("account1", "id_for_type_b")));
      
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
          return -1;
      
        return app.exec();
      }
      
      #include "main.moc"
      
      import QtQuick 2.10
      import QtQuick.Window 2.10
      
      Window {
        visible: true
        width: 800
        height: 480
        title: qsTr("TypedDescriptor Q_GADGET Demo")
      
        Text {
          text: "Descriptors in Qml land:<br><br>" +
                "generic: " + descriptor +   " - " + descriptor.accountId    + " toString " + descriptor.toString() + "<br>" +
                "type a   " + descriptor_a + " - " + descriptor_a.accountId  + " toString " + descriptor_a.toString() + "<br>" +
                "type b   " + descriptor_b + " - " + descriptor_b.accountId  + " toString " + descriptor_b.toString()
        }
      }
      
      

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            ogoffart Olivier Goffart (Woboq GmbH)
            njeisecke Nils Jeisecke
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes