-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve manual implementations of up- and downcasting #1204
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #1204 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 73 73
Lines 12612 12612
=========================================
Hits 12612 12612 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
d144680
to
6d63031
Compare
eeab59b
to
dee86e9
Compare
dee86e9
to
00886c0
Compare
00886c0
to
311ddd9
Compare
@@ -22,7 +23,15 @@ const Sub* | |||
downcastPtr(const Base* base) | |||
{ | |||
static_assert(std::is_base_of_v<Base, Sub>); | |||
return dynamic_cast<const Sub*>(base); | |||
return qobject_cast<const Sub*>(base); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we want to use qobject_cast ? As this then limits to only based QObject classes, but there are classes in Qt we need to supportt that do not have QObject as a base ?
Or we are going to need some logic in the generation of the automatic up/down casts to figure out whether it should use a qobject cast or not (maybe if there is a #[qobject]
attribute or not).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What we shouldn't use is dynamic_cast
. There are only a few cases of Qt class inheritance that isn't based on a QObject
, and those should use static_cast
, as with QPolygon
and QStringList
. This is due to the complicated way that C++ handles RTTI and polymorphism. dynamic_cast
only works if the class has at least one virtual method so it can check RTTI (and it has some other esoteric limitations that make it a poor tool for almost any task). For Qt, that means it wouldn't work unless it's a QObject
anyway. That's why downcasting QStringList
didn't work using dynamic_cast
.
Given the #[base]
pragma being added by #1202, I think we would probably want to break it down like this:
- If the class inherits
QObject
, use#[base]
, which internally usesqobject_cast
. This can be done both internally and by end users. Probably don't automatically implementDeref
as it'd be too complicated. Plus,QObject
s can't implementDerefMut
because their mutable methods usePin<&mut Self>
. - Otherwise, use
static_upcast
from thoseextern
utility functions and implementDeref
andDerefMut
. (DerefMut
can be implemented because their mutable methods use&mut self
.) This should only be done internally, because otherwise it might get added to something that isn't actually valid to cast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we're talking about downcasting, static_cast
is a no-go in the general case, we do not know if the type is actually of the subclass, so we need to check at runtime. static_cast
would do the cast in any case, which is instant undefined behavior if the type is not actually of the subclass.
dynamic_cast
is the only option we have for non-QObject types in that case, even given it's limitations.
@jnbooth you bring up some good points though, which we should integrate.
- We can use is_polymorphic to assert that a type actually has at least one virtual function so we can use dynamic_cast.
- We can use template meta-programming with is_base_of to detect if a type is derived from QObject and then use qobject_cast instead of dynamic_cast.
That way, we can still have a single templated method that uses the optimal cast in all cases.
@jnbooth If you want I can send you the required code, in case you don't want to deal with the template meta programming. Just let me know.
For types that don't meet these criteria, we likely don't want do support downcast?
I guess we could just always return nullptr
there, which would turn into None
in the Rust impl.
@ahayzen-kdab may need to respect this in #1202 . 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, I'm not suggesting static_cast
should be used in the general case or any generated code. I am only using it here, manually implemented, for cases where static_cast
is always safe, per its safety requirements. downcastPtrStatic
is never used anywhere else, nor should it be.
Are there any examples you have of types that might need to be downcasted that aren't QObjects, aside from the above special cases of classes that are always safe to static_cast
(there are only a few of them)? I think it is probably a better idea to handle those few cases internally and use qobject_cast
for everything else, because users can only use QObject
s anyway. dynamic_cast
is generally not a good idea, to the point that it is sometimes labeled "evil".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For other examples, you might have a class that is a gadget or something that is just a raw class in Qt like an implementation of QQuickFramebufferObject::Renderer
. You may then take the base class as a parameter and want to downcast potentially to your class.
I agree where we have #[qobject]
on the class we should be able to determine that we can use qobject_cast
.
So i think we then potentially end up with the following
static_cast
when safe to do so (eg type is of same size and derived from the other etc)qobject_cast
when QObject is a base class of the objectdynamic_cast
for anything else that does not have a QObject base or does not have same size or is not derived from the other etc
@jnbooth Thank you for the contribution, this is looking good. Regarding the downcasting, I think we can get all those concerns implemented in a sane way with a bit of meta-programming that automatically selects between qobject_cast and dynamic_cast. And keeping static_cast for the special cases is okay, though I think we should add another assertion just out of caution. |
@LeonMatthesKDAB I don't know how to do that kind of metaprogramming. Are there examples of it in the codebase that I could learn from? Alternatively, should I strip down part of this PR so it can be merged without that? |
Add detection whether to use qobject_cast or dynamic_cast via template specialization. Also add additional checks that a static_cast downcast is sane to do (i.e. at least the size of the types must be the same).
@jnbooth This should do it: I pushed this to a branch on my fork (non-qobject-casting) and rebased, feel free to cherry-pick or just use that branch for your PR. My template meta-programming is a bit rusty nowadays, so @ahayzen-kdab please review carefully to check this is sane. |
This PR provides some minor improvements to #1159 for the implementations of
Upcast
.downcastPtr
's use ofdynamic_cast
withqobject_cast
, which is much faster and does not require RTTI.Upcast<QVector<QPoint>>
forQPolygon
, replacing the standalone functions previously used forDeref
andDerefMut
.Upcast<QVector<QPointF>>
forQPolygonF
, replacing the standalone functions previously used forDeref
andDerefMut
.Implements¹Upcast<QCoreApplication>
forQGuiApplication
and addsDeref
.Implements¹Upcast<QGuiApplication>
forQApplication
and addsDeref
.Implements¹Deref
forQQmlApplicationEngine
toQQmlEngine
using the existing upcasting implementation.QStringList
's implementation ofUpcast<QList<QString>>
due to a misuse ofdynamic_cast
.¹ Removed in favor of #1202.