why qt6 add more compiled-time check and caused some unexpected errors
-
If a template class A
template<typename T> class A;
it has a friend template operator ==
template <typename E> friend bool operator ==(const A&,const A<E>&){…}
But in my project I haven't called this function in the code, So this code will not be generated by the compiler in the end. Even though underlying type T or E isn’t overloading operator ==, this code should pass compiler check.
But Qt6 will generate some code by moc to check these types have some function or not, It seems that moc can't determine whether the code is finally generated by the compiler. -
@ComixHe said:
I don't think moc should generate some check code
It's not a check code. It generates code that builds a meta object, which is a runtime reflection system. See here. It needs to do that, otherwise the runtime introspection wouldn't work, and that's one of main features of QObjects.
for template functions that are not generated at the end
They are, just not by your code. Meta system stores function pointers to these methods and thus instantiates them. It's similar to standard type traits and rtti mechanisms combined, just richer.
Is this because moc is not a compiler and therefore cannot make a determination?
Moc is just a boilerplate code generator. It doesn't do much logic on its own, just sees macros like Q_OBJECT and generates some method implementations for that class. There's no magic here, it's all standard C++. It saves it to a file in your build dir, so you can look at the code it generates if you want (although it can be a bit hard to read due to its complexity).
I'm just curious why qt6 added type checking for slot functions and qt5 didn't
Because the functionality of meta system was expanded in Qt6. One of the addition was possibility to compare meta types. See QMetaType::equals.
There's nothing wrong with Qt here. It's just that your code was ambiguous and has paths that lead to compilation errors. Nothing in Qt5 relied upon it being specific so it worked. Qt6 asks more specific questions and it exposed a bug. Simply be more specific. Define the operator correctly or delete/enable_if it out, so that Qt can't find it if it's not supposed to. Think of it like this - if Qt can cause an instantiation of your template fail to compile then anyone can. It's a bug you need to fix. Qt6 is just the messenger here, so don't shoot it.
-
@jsulm no, i want to implement a wrapper template class from some custom type
example:
struct NewConnection{ quint64 connId; QString ConnInfo; }; template<typename T> class A{ public: ... // some functions template <typename E> friend bool operator==(const A &_x, const A<E> &_e) { return bool(_x.value() == _e.value()); } // some functions ... }; class B : public QObject{ Q_OBJECT: public: A<NewConnection> func() {}; }
Then moc will generate some template code for compile-time checking
qt_incomplete_metaTypeArray<qt_meta_stringdata_xxx__xxxx__classxxx_t , QtPrivate::TypeAndForceComplete<A<NewConnection>, std::true_type>,...>
-
Your (slightly modified code) compile fine for me:
#include <QtCore> struct NewConnection { quint64 connId; QString ConnInfo; }; template<typename T> class A { public: template <typename E> friend bool operator==(const A& _x, const A<E>& _e) { return bool(_x.value() == _e.value()); } }; class B : public QObject { Q_OBJECT public: A<NewConnection> func() {}; }; int main(int argc, char** argv) { QCoreApplication app(argc, argv); B b; return 0; } #include "main.moc"
So please provide a minimal, compilable example to reproduce the error.
-
@Christian-Ehrlicher
code:#include <QObject> #include <QCoreApplication> struct NewConnection{ quint64 connId; double connTmp; }; template <typename T> class A{ public: A() = default; template <typename E> friend bool operator==(const A& _x, const A<E>& _e) { return bool(_x.value() == _e.value()); } private: T value; }; class Test:public QObject{ Q_OBJECT public: explicit Test(QObject *parent = nullptr); public slots: A<NewConnection> func() {return {};}; }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Test t; auto ret = t.func(); return a.exec(); } #include "main.moc"
gcc version:12.2.0;
Qt version: 6.4.1compiler outputs:
[3/4] Building CXX object CMakeFiles/untitled.dir/main.cpp.o FAILED: CMakeFiles/untitled.dir/main.cpp.o /usr/bin/c++ -DQT_CORE_LIB -DQT_NO_DEBUG -I/home/comix/Qt/untitled/build/untitled_autogen/include -isystem /usr/include/qt6/QtCore -isystem /usr/include/qt6 -isystem /usr/lib/qt6/mkspecs/linux-g++ -O3 -DNDEBUG -fPIC -std=gnu++17 -MD -MT CMakeFiles/untitled.dir/main.cpp.o -MF CMakeFiles/untitled.dir/main.cpp.o.d -o CMakeFiles/untitled.dir/main.cpp.o -c /home/comix/Qt/untitled/main.cpp /home/comix/Qt/untitled/main.cpp: In instantiation of ‘bool operator==(const A<T>&, const A<E>&) [with E = NewConnection; T = NewConnection]’: /usr/include/qt6/QtCore/qmetatype.h:2224:46: required from ‘static bool QtPrivate::QEqualityOperatorForType<T, <anonymous> >::equals(const QtPrivate::QMetaTypeInterface*, const void*, const void*) [with T = A<NewConnection>; bool <anonymous> = true]’ /usr/include/qt6/QtCore/qmetatype.h:2359:54: required from ‘constexpr const QtPrivate::QMetaTypeInterface QtPrivate::QMetaTypeInterfaceWrapper<A<NewConnection> >::metaType’ /usr/include/qt6/QtCore/qmetatype.h:2486:16: required from ‘constexpr const QtPrivate::QMetaTypeInterface* QtPrivate::qTryMetaTypeInterfaceForType() [with Unique = {anonymous}::qt_meta_stringdata_Test_t; TypeCompletePair = TypeAndForceComplete<A<NewConnection>, std::integral_constant<bool, false> >]’ /usr/include/qt6/QtCore/qmetatype.h:2537:55: required from ‘constexpr const QtPrivate::QMetaTypeInterface* const qt_incomplete_metaTypeArray [2]<{anonymous}::qt_meta_stringdata_Test_t, QtPrivate::TypeAndForceComplete<Test, std::integral_constant<bool, true> >, QtPrivate::TypeAndForceComplete<A<NewConnection>, std::integral_constant<bool, false> > >’ /home/comix/Qt/untitled/build/untitled_autogen/include/main.moc:79:5: required from here /home/comix/Qt/untitled/main.cpp:15:29: error: no match for call to ‘(const NewConnection) ()’ 15 | return bool(_x.value() == _e.value()); | ~~~~~~~~^~ /home/comix/Qt/untitled/main.cpp:15:43: error: no match for call to ‘(const NewConnection) ()’ 15 | return bool(_x.value() == _e.value()); | ~~~~~~~~^~ ninja: build stopped: subcommand failed.
moc generates code:
Q_CONSTINIT const QMetaObject Test::staticMetaObject = { { QMetaObject::SuperData::link<QObject::staticMetaObject>(), qt_meta_stringdata_Test.offsetsAndSizes, qt_meta_data_Test, qt_static_metacall, nullptr, qt_incomplete_metaTypeArray<qt_meta_stringdata_Test_t, // Q_OBJECT / Q_GADGET QtPrivate::TypeAndForceComplete<Test, std::true_type>, // method 'func' QtPrivate::TypeAndForceComplete<A<NewConnection>, std::false_type> >, nullptr } };
-
As @JoeCFD said - returning something in a slot is not something a slot should do.
Maybe you can ask at the mailing list because there are the ones who know much more about templates and the moc stuff than here but I would guess you will get the same answer - don't return something in a slot, esp. not a template.Works fine for me with msvc when the compare operator is corrected - value is not a function.
-
@ComixHe said:
But in my project I haven't called this function in the code, So this code will not be generated by the compiler in the end
Your code doesn't, but Qt will. When you mark a class as Q_OBJECT Qt's moc will generate code that builds a QMetaObject for that class, which is a way to have runtime reflection of a type. The
Test::func
is indexed in the meta object as aQMetaMethod
of typeQMetaMethod::Slot
. Part of that indexing is also recognizing function parameters and return type asQMetaType
. See QMetaMethod::returnMetaType(). Moving further the type is categorized and all relevant traits of it are checked. One of those traits is if the type is comparable i.e. if it has a comparison operator. See QMetaType::isEqualityComparable().So long story short, as part of that meta class generation an address of the comparison operator of a return type of a method of a Q_OBJECT class is instantiated, which is
bool A<NewConnection>::operator==<NewConnection>(const A<NewConnection>& _x, const A<NewConnection>& _e)
So both
_x
and_e
are of typeA<NewConnection>
, which does not have avalue()
method, so compilation fails.Couple ways to fix this:
- like @Christian-Ehrlicher said,
value
is a member variable and not a function. If it's just a mistake then remove()
from the comparison and it will compile. - Don't unconditionally assume arguments have
value()
method. You can check it with enable_if, or, if you're on C++20, with a concept. - If
A<NewConnection>
should be comparable create a template specialization for the operator == that takesA<NewConnection>
as_e
parameter and implement comparison using only the contents of that class.
- like @Christian-Ehrlicher said,
-
@Chris-Kawa
In fact, I know the reason for the moc error, and I also know how to fix this problem.
I'm sorry I forgot before that there should be a value method in this template classtemplate <typename T> class A { public: A() = default; template <typename E> friend bool operator==(const A& _x, const A<E>& _e) { return bool(_x.value() == _e.value()); } const T& value() const { return m_value; } private: T m_value; };
and now
/home/heyuming/workspace/demo/demo.cpp:16:28: error:no match for ‘operator==’ (operand types are ‘const NewConnection’ and ‘const NewConnection’) 16 | return bool(_x.value() == _e.value());
For some reason, I can only use C++17 and such functions must be placed under slots. I'm just curious why qt6 added type checking for slot functions and qt5 didn't.
I don't think moc should generate some check code for template functions that are not generated at the end. Is this because moc is not a compiler and therefore cannot make a determination?
I am a newbie who just learned Qt, I don't understand much about moc, maybe there are some misunderstandings in it...
Thanks~
-
@ComixHe said:
I don't think moc should generate some check code
It's not a check code. It generates code that builds a meta object, which is a runtime reflection system. See here. It needs to do that, otherwise the runtime introspection wouldn't work, and that's one of main features of QObjects.
for template functions that are not generated at the end
They are, just not by your code. Meta system stores function pointers to these methods and thus instantiates them. It's similar to standard type traits and rtti mechanisms combined, just richer.
Is this because moc is not a compiler and therefore cannot make a determination?
Moc is just a boilerplate code generator. It doesn't do much logic on its own, just sees macros like Q_OBJECT and generates some method implementations for that class. There's no magic here, it's all standard C++. It saves it to a file in your build dir, so you can look at the code it generates if you want (although it can be a bit hard to read due to its complexity).
I'm just curious why qt6 added type checking for slot functions and qt5 didn't
Because the functionality of meta system was expanded in Qt6. One of the addition was possibility to compare meta types. See QMetaType::equals.
There's nothing wrong with Qt here. It's just that your code was ambiguous and has paths that lead to compilation errors. Nothing in Qt5 relied upon it being specific so it worked. Qt6 asks more specific questions and it exposed a bug. Simply be more specific. Define the operator correctly or delete/enable_if it out, so that Qt can't find it if it's not supposed to. Think of it like this - if Qt can cause an instantiation of your template fail to compile then anyone can. It's a bug you need to fix. Qt6 is just the messenger here, so don't shoot it.
-
@Chris-Kawa Thank you very much for your detailed answer
-
@ComixHe No problem. Oh, you also said that you now need C++17. Yes, the minimum language version requirement in Qt6 was bumped from C++11 to C++17. It makes a lot stuff easier, the implementation can be cleaner and it is supported by all major compilers anyway.