Qt Forum

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Unsolved

    Unsolved Undocumented automatic metatype registration in Qt6?

    General and Desktop
    metatype signals & slots qvariant qt6
    2
    5
    665
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • oblivioncth
      oblivioncth last edited by oblivioncth

      In an effort to dig deeper in this, I came across a situation that I do not understand given the documentation for QMetaType.

      MyStruct.h

      #ifndef MYSTRUCT_H
      #define MYSTRUCT_H
      
      #include <QMetaType>
      
      struct MyStruct
      {
          int val;
      };
      #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
          Q_DECLARE_METATYPE(MyStruct);
      #endif
      
      
      #endif // MYSTRUCT_H
      

      MyObject.h

      #ifndef MYOBJECT_H
      #define MYOBJECT_H
      
      #include <QObject>
      #include <QDebug>
      #include "mystruct.h"
      
      class MyObject : public QObject
      {
          Q_OBJECT
      
          Q_PROPERTY(MyStruct value READ value WRITE setValue NOTIFY valueChanged)
      
      private:
          MyStruct mValue;
      
      public:
          MyObject() {}
          MyStruct value() { return mValue; }
      
      signals:
          void valueChanged(MyStruct newValue);
      
      public slots:
          void setValue(MyStruct newValue)
          {
              mValue = newValue;
              emit valueChanged(mValue);
              qDebug() << qint64(this) << "Update: " << mValue.val;
          }
      };
      #endif // MYOBJECT_H
      

      main.cpp

      #include <QCoreApplication>
      #include <QTimer>
      #include <QVariant>
      #include "myobject.h"
      #include "mystruct.h"
      
      int main(int argc, char *argv[])
      {
          QCoreApplication a(argc, argv);
      
          // Test Signal/Slot connection
          MyObject oA = MyObject();
          MyObject oB = MyObject();
          bool connected = static_cast<bool>(QObject::connect(&oA,
                                                              &MyObject::valueChanged,
                                                              &oB,
                                                              &MyObject::setValue,
                                                              Qt::QueuedConnection
          ));
          qDebug() << "Connection?: " << connected;
          oA.setValue(MyStruct{777});
      
          // Test Variant
          MyStruct sA{4};
          QVariant var;
          var.setValue(sA);
          MyStruct sB = var.value<MyStruct>();
          assert(sA.val == sB.val);
      
          // Automatically end
          QTimer::singleShot(1000, &a, &QCoreApplication::quit);
      
          return a.exec();
      }
      
      

      Example Output with Qt 5.12.3 and 6.2.4.

      06:23:02: Starting MetaTypeIssue.exe...
      Connection?:  true
      88140151592 Update:  777
      88140151640 Update:  777
      06:23:03: MetaTypeIssue.exe exited with code 0
      

      I'm confused as to why this code compiles and runs correctly with Qt6. It's as if the requirement to use Q_DECLARE_METATYPE was removed, even though the docs certainly do not reflect that.

      As an aside, I'm also confused as to why this compiles/runs in either case without a call to qRegisterMetaType<MyStruct>(); since the connection type is queued. If I compile with Qt5 and remove the Q_DECLARE_METATYPE() call and QVariant code, the code runs and gives the appropriate runtime warning that MyStruct needs to be registered with qRegisterMetaType<>(), but if I just put back Q_DECLARE_METATYPE() without it then the warning goes away and the connection is created successfully.

      With Qt6 neither is required.

      kshegunov 1 Reply Last reply Reply Quote 0
      • kshegunov
        kshegunov Moderators @oblivioncth last edited by

        @oblivioncth said in Undocumented automatic metatype registration in Qt6?:

        I'm confused as to why this code compiles and runs correctly with Qt6. Its as if the requirement to use Q_DECLARE_METATYPE was removed, even though the docs certainly do not reflect that.

        Your original code (in the top-mentioned topic) assumes std::unique_ptr is copyable, which it isn't, so it can not ever be a metatype. Although I'm not sure what you mean by "correctly" as you haven't provided the qDebug() log.

        As an aside, I'm also confused as to why this compiles/runs in either case without a call to qRegisterMetaType<MyStruct>(); since the connection type is queued.

        It should compile and run with warnings at runtime.

        If I compile with Qt5 and remove the Q_DECLARE_METATYPE() call and QVariant code, the code runs and gives the appropriate runtime warning that MyStruct needs to be registered with qRegisterMetaType<>(), but if I just put back Q_DECLARE_METATYPE() without it then the warning goes away and the connection is created successfully.

        The connection statement is going to be compiled successfully either way if the signatures match, this doesn't mean the connection is correctly established, or that it can be processed at runtime. QObject::connect actually returns an object, which can also be tested for validity.

        Read and abide by the Qt Code of Conduct

        1 Reply Last reply Reply Quote 0
        • oblivioncth
          oblivioncth last edited by oblivioncth

          @kshegunov said in Undocumented automatic metatype registration in Qt6?:

          Your original code (in the top-mentioned topic) assumes std::unique_ptr is copyable, which it isn't, so it can not ever be a metatype. Although I'm not sure what you mean by "correctly" as you haven't provided the qDebug() log.

          My apologies, that was a leftover from experimentation. The metatype declaration was supposed to be for std::shared_ptr<int>, as is used everywhere else in the code posted there. The attached example has the correct statement and exhibits the issue. I've corrected that post. As for the output of the code in this post, see below.

          It should compile and run with warnings at runtime.

          Yes, as I said in the section you quoted next, it does give a runtime warning specifying that qRegisterMetaType needs to be used, but this is only present if Q_DECLARE_METATYPE is missing as well. If I use the latter, the warning also disappears, even though Q_DECLARE_METATYPE doesn't imply the former (i.e. they're distinct and in theory both should be required here).

          To clarify, without the QVariant section (since that won't compile with Qt5 in some of these cases):

          Qt5
          Without Q_DECLARE_METATYPE and without qRegisterMetaType: Runtime warning, slot isn't called.
          With Q_DECLARE_METATYPE and without qRegisterMetaType: No warning, slot is called
          With Q_DECLARE_METATYPE and with qRegisterMetaType: No warning, slot is called

          Qt6
          Without Q_DECLARE_METATYPE and without qRegisterMetaType: No warning, slot is called
          With Q_DECLARE_METATYPE and without qRegisterMetaType: No warning, slot is called
          With Q_DECLARE_METATYPE and with qRegisterMetaType: No warning, slot is called

          So in both cases qRegisterMetaType isn't required for the slot to be called and the custom type to be accessible within the slot (i.e. no unexpected garbage data or the like).

          The connection statement is going to be compiled successfully either way if the signatures match, this doesn't mean the connection is correctly established, or that it can be processed at runtime. QObject::connect actually returns an object, which can also be tested for validity.

          In this case I truly meant correctly. I've modified the example slightly to leverage connect's return and show example output. Unfortunately it just cements my bewilderment that everything is working "as expected" with no call to qRegisterMetaType in either version, and no call to Q_DECLARE_METATYPE in Qt6. I'd say I must be misunderstanding something with the documentation, but it seems fairly explicit to me.

          kshegunov 1 Reply Last reply Reply Quote 0
          • kshegunov
            kshegunov Moderators @oblivioncth last edited by

            I suspect the struct being public and relying on the default constructor(s)/destructor may be getting you a 'free pass'. I'd suggest you do your experiments with a class/struct that has out-of-line (copy) constructor implementation (i.e. in a separate translation unit) to prevent the compiler from accessing and inlining it whenever it needs/feels/can.

            Read and abide by the Qt Code of Conduct

            1 Reply Last reply Reply Quote 0
            • oblivioncth
              oblivioncth last edited by oblivioncth

              @kshegunov
              Spiced it up a bit in accordance with your suggestion.

              CMakeLists.txt

              cmake_minimum_required(VERSION 3.19)
              
              project(MetaTypeIssue VERSION 0.1 LANGUAGES CXX)
              
              set(CMAKE_CXX_STANDARD 17)
              set(CMAKE_CXX_STANDARD_REQUIRED ON)
              
              set(CMAKE_AUTOMOC ON)
              
              find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
              find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)
              
              set(PROJECT_SOURCES
                      main.cpp
                      myclass.h
                      myobject.h
                      myclass.cpp
                      myobject.cpp
              )
              if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
                  qt_add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
              else()
                  add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
              endif()
              
              target_link_libraries(MetaTypeIssue PRIVATE Qt${QT_VERSION_MAJOR}::Core)
              

              myclass.h

              #ifndef MYCLASS_H
              #define MYCLASS_H
              
              #include <QMetaType>
              
              #define HEX_ADDR(ptr) QString("0x%1").arg(quint64(ptr), 0, 16)
              
              class MyClass
              {
              private:
                  int mValue;
              
              public:
                  MyClass();
                  MyClass(int value);
                  MyClass(const MyClass& other);
                  ~MyClass();
              
                  int value();
              };
              #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
                  Q_DECLARE_METATYPE(MyClass);
              #endif
              
              
              #endif // MYCLASS_H
              

              myclass.cpp

              #include "myclass.h"
              #include <QDebug>
              
              MyClass::MyClass() {}
              MyClass::MyClass(int value) { mValue = value; }
              MyClass::~MyClass()
              {
                  qDebug() << HEX_ADDR(this) << "dead";
              }
              MyClass::MyClass(const MyClass& other)
              {
                  mValue = other.mValue;
                  qDebug() << HEX_ADDR(this) << "Copied from: " << HEX_ADDR(&other);
              }
              int MyClass::value() { return mValue; }
              

              myobject.h

              #ifndef MYOBJECT_H
              #define MYOBJECT_H
              
              #include <QObject>
              #include <QDebug>
              #include "myclass.h"
              
              class MyObject : public QObject
              {
                  Q_OBJECT
              
                  Q_PROPERTY(MyClass value READ value WRITE setValue NOTIFY valueChanged)
              
              private:
                  MyClass mValue;
              
              public:
                  MyObject();
                  MyClass value();
              
              signals:
                  void valueChanged(MyClass newValue);
              
              public slots:
                  void setValue(MyClass newValue);
              };
              
              #endif // MYOBJECT_H
              

              myobject.cpp

              #include "myobject.h"
              
              MyObject::MyObject() {}
              
              MyClass MyObject::value() { return mValue; }
              
              void MyObject::setValue(MyClass newValue)
              {
                  mValue = newValue;
                  emit valueChanged(mValue);
                  qDebug() << HEX_ADDR(this) << "Update: " << mValue.value();
              }
              

              main.cpp

              #include <QCoreApplication>
              #include <QTimer>
              #include <QVariant>
              #include "myobject.h"
              #include "myclass.h"
              
              int main(int argc, char *argv[])
              {
                  QCoreApplication a(argc, argv);
              
                  // Test Signal/Slot connection
                  qDebug() << "Setup Signal & Slot test";
                  MyObject oA;
                  MyObject oB;
                  bool connected = static_cast<bool>(QObject::connect(&oA,
                                                                      &MyObject::valueChanged,
                                                                      &oB,
                                                                      &MyObject::setValue,
                                                                      Qt::QueuedConnection
                  ));
                  qDebug() << "Connection ?: " << connected;
                  oA.setValue(MyClass(777));
              
                  // Test Variant
                  qDebug() << "Test Variant";
                  MyClass sA(4);
                  QVariant var;
                  var.setValue(sA);
                  MyClass sB = var.value<MyClass>();
                  assert(sA.value() == sB.value());
              
                  // Automatically end
                  qDebug() << "Setup quit";
                  QTimer::singleShot(1000, &a, &QCoreApplication::quit);
              
                  return a.exec();
              }
              

              Sample Output:

              Setup Signal & Slot test
              Connection ?:  true
              "0xfed5cff460" Copied from:  "0xfed5cff5e8"
              "0x21aaecfe8a0" Copied from:  "0xfed5cff460"
              "0xfed5cff460" dead
              "0xfed5cff5d8" Update:  777
              "0xfed5cff708" dead
              Test Variant
              "0x21aaecfc358" Copied from:  "0xfed5cff634"
              "0xfed5cff694" Copied from:  "0x21aaecfc358"
              Setup quit
              "0xfed5cfb2d0" Copied from:  "0x21aaecfe8a0"
              "0xfed5cfb1a0" Copied from:  "0xfed5cff618"
              "0xfed5cfb1a0" dead
              "0xfed5cff608" Update:  777
              "0xfed5cfb2d0" dead
              "0x21aaecfe8a0" dead
              "0xfed5cff694" dead
              "0x21aaecfc358" dead
              "0xfed5cff634" dead
              "0xfed5cff618" dead
              "0xfed5cff5e8" dead
              

              Still magical with both versions of Qt :)

              Will try to pry some more.

              EDIT:
              Ensured that /Ob0 (MSVC) was set to completely disable inlining and tried changing the data member of MyClass from an int to a QString. Situation is still the same.

              1 Reply Last reply Reply Quote 0
              • First post
                Last post