QtCS25 - QProperty and QBindable
Session Summary
Without integration with moc, Q_PROPERTY and QML, QProperty and QBindable would be fairly simple and self-contained pieces of code. They could be moved to a separate library, e.g. QtBindable and live there without hurting anyone.
Conversely, the deep integration with moc, Q_PROPERTY and QML gives us a lot of headaches.
- QBindingStorage increases the size of all QObjects by two pointers.
- Granted: Simple properties are easy to use
- Just slap a BINDABLE on it and make the getter and setter "default".
- Spurious dependencies when when writing custom setters:
- whenever you need to check some (other) property's value, you need valueBypassingBindings()
- ditto when producing argument for change signal
- If setters have side effects, you need to do, in the right places:
- removeBindingUnlessInWrapper()
- setValueBypassingBindings()
- notify()
- Easy to get wrong and results in subtle, hard to detect problems.
- QML engine has to handle all permutations of QProperty and classic properties
- bloats code
- increases complexity in many places
- QProperty not noticably faster when used from QML
Why QProperty anyway?
Offer a convenient C++ API for bindings.
- QProperty and QPropertyBinding work great on their own
- very ergonomic
- quite performant
- moc/Q_PROPERTY integration is extra you don't really need
- QProperty and QPropertyBinding work fine without any QObject
- external adapter makes getter/setter/signal Q_PROPERTY integrate with QProperty
- Special constructors for QBindable that take an object/property pair
Enable Qt Quick Compiler to generate better C++ code for bindings
Has steep preconditions:
- binding needs to be compilable
- need to use direct mode
- implies private API
- implies accessible headers
- property written has to be bindable
- properties read from have to be bindable
In practice this rarely happens.
Suggestions
Ulf
- Warn about properties with BINDABLE but without NOTIFY in QML
- Those become a problem at step 4
- You can easily add change signals to bindable properties, though
- Deprecate the BINDABLE attribute to Q_PROPERTY. We probably can't have moc produce a warning about it right away, but we can already adapt the documentation.
- Deprecate QObjectBindableProperty and friends.
- Revert our own bindable Q_PROPERTYs to be backed by simple data members rather than QProperty again. Implement the public fooBindable() methods in terms of the Q_PROPERTY adaptors for QBindable and deprecate them. Drop all the BINDABLEs in private API.
- Remove the integration with QProperty and QBindable in QML. QML will always use the READ/WRITE/NOTIFY methods again and ignore any BINDABLE.
- In Qt7, move the remaining QProperty and QBindable code into a separate library.
Undecided: Using BINDABLE in simple Q_PROPERTY is much less boiler plate than READ/WRITE/NOTIFY. We need a replacement for that.
Volker
Yes, remove Q_PROPERTY integration of QProperty via some migration process.
Simplified Q_PROPERTY declaration that removes the getter/setter boilerplate from Q_PROPERTY
class Object : public QObject { Q_OBJECT Q_PROPERTY(value) public: // ~~~ int value() const; void setValue() const; signals: void valueChanged(); };
- Pro: Simpler Q_PROPERTY declaration
- Con:
- You still need to write the setters, getters, and signals. That's more boilerplate.
- Tying the naming together is magic
Thiago
class Object : public QObject { Q_OBJECT public: Q_PROPERTY_FULL(int, READ, int value() noexcept, WRITE, void setValue(int), NOTIFY, void valueChanged()) };
It shouldn't be too difficult to make the macro output all positive even- numbered fields into the header. Or if we can find a way to insert a prefix that then turns the word into a macro that expands to empty:
Q_PROPERTY_FULL(int, READ int value() noexcept, WRITE void setValue(int), NOTIFY void valueChanged())
- Pro: Removes all the boilerplate from the header
- Con:
- Extremely elaborate macro
- What about the implementations of getters and setters?
Ulf
Why all the ceremony? What people probably want is:
Q_DEFAULT_PROPERTY(int, value)
That would expand to:
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged FINAL) public: int value() const { return m_value; } void setValue(int value) { if (value == m_value) return; m_value = value; Q_EMIT valueChanged(); } Q_SIGNALS: void valueChanged(); private: int m_value = {};
Now you just need to make sure you keep your Q_DEFAULT_PROPERTY in the private section of your class and you're fine. The capital "V" in "setValue" is probably challenging to express in a macro but otherwise this should be trivial.
If we can't express the 'V', let's make it Q_DEFAULT_PROPERTY(int, value, setValue). That's still quite good compared to what we have!
Pro: Removes actually all the boilerplate Con: * Even more elaborate macro (but not much logic involved) * Does not cater to custom naming schemes, readonly, constant, etc
NB: We can have Q_CONSTANT_PROPERTY, Q_READONLY_PROPERTY and maybe Q_CUSTOM_PROPERTY in addition. Q_CUSTOM_PROPERTY would specify all the getter, setter, signal names. We can even have Q_CUSTOM_CONSTANT_PROPERTY and Q_CUSTOM_READONLY_PROPERTY, but I'll stop here.
Session Owners
Ulf Hermann
Notes
- introduced with big fanfare in Qt 6
- QProperty/QBindable quite nice, but its own universe
- idea: bring QML technology to C++
- can't roll it out to our own properties
- so we didn't
- akward: both technologies co-exist in QML now
- assumption: very few people are using it (to its full extent)
- if we want to remove it in Qt 7, we need to deprecate it
- bringing that up caused outcry:
- We need an equally way to expose properties
- only used for that?
- some ideas were brought up to fix it
- for instance a mega-marco which creates "everything" for a simple property
- With such a marco, could we deprecate properties?
- what about change signals, not wanting a change signal
- need a search&replace way to move forward
- devil's advocate: it's not only about boilerplate, people also want to have the reactive properties
- no public API to have setters with custom logic; real world use cases not covered by
- would people actually use it then?
- actual real world use cases were listed, multiple C++ QProperty; one Q_PROPERTY whose getter uses them, exposed to QML
-> update one C++ property results in automatic propagation to QML
- there are already libraries out there for saving boilerplate
- remove bindable from QObject, still keep QProperty as a class, but provide a bridge to QML?
- what do we tell our users then? How would it like in the end?
- In QObject: only signals, no BINDABLE
- But internal logic can still use QProperty
- Side questions: Concept of a property in other language (in the Qt brigdes context)?
- we want to use the native property support in other languages
- signals on value types discussion / discussion about value types