Undocumented automatic metatype registration in Qt6?
-
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 theQ_DECLARE_METATYPE()
call and QVariant code, the code runs and gives the appropriate runtime warning that MyStruct needs to be registered withqRegisterMetaType<>()
, but if I just put backQ_DECLARE_METATYPE()
without it then the warning goes away and the connection is created successfully.With Qt6 neither is required.
-
@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 theqDebug()
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. -
@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 theqDebug()
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 thoughQ_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 calledQt6
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 calledSo 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 toqRegisterMetaType
in either version, and no call toQ_DECLARE_METATYPE
in Qt6. I'd say I must be misunderstanding something with the documentation, but it seems fairly explicit to me. -
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.
-
@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 ofMyClass
from an int to a QString. Situation is still the same. -
@oblivioncth same here. Neither Macro nor register function needed. no related documents.
-