C++ QProperty binding Question
-
Following is the code snippet. Code is working properly. No issue with code. I have understanding issue. How this internally works.
QProperty<int> height { 50}; QProperty<int> width { 100}; QProperty<int> area; area.setBinding([&](){ qDebug() << " This is called one more time" << Qt::endl; return width * height; }); width = 4; height = 40; width = 10;
How the change in width or height triggers the re-evaluate of area ?
Area is binded to lambda expression. Inside the lambda we have reference to width & height. How the notify(change) signal of width or height is bound to area ? Is there any parsing happens for lambda expression, finds the width or height & then it does the some thing ?
Looking for an answer on binding is built internally. -
@J-Hilk said in C++ QProperty binding Question:
Internally, when you call setBinding, the QProperty object can inspect the lambda expression and identify any external variables that are captured by reference. In your lambda & { return width * height; }, width and height are captured by reference (&), indicating that they are external variables used inside the lambda body.
No.
The QProperty knows its dependency because when you call setBinding with a lambda, the lambda is executed and it tracks the other properties that are being evaluated in it.
If both
width
andheight
areQProperty<int>
.
width * height
is first calling the implicitQProperty<int>::operator int()
for both, converting them toint
so it can call the arithmetic operator * on their values. This operator int() is what is registering the dependency.To put it otherwise, when calling
area.setBinding([&](){ return width * height; });
here is what happens:- the lambda is stored in the area QProperty to be called later.
area
is marked as the current property being evaluated.- the lambda is then executed.
width
is being accessed through its conversion operator, it is thus being registered as a dependency of the currently evaluated property (area
here)- the same happens for
height
- the return value of the lambda is assigned to
area
area
is no longer marked as the currently evaluated property.
After that, every time that
width
andheight
are changed, the lambda is being reevaluated and the above process redone. -
@dheerendra
Just a quick answer from me, I don't use QML so you may get a better answer from someone who does, I don't want to take up too much space.https://doc.qt.io/qt-6/qtqml-syntax-propertybinding.html does say:
When a property's dependencies change in value, the property is automatically updated according to the specified relationship.
Behind the scenes, the QML engine monitors the property's dependencies (that is, the variables in the binding expression). When a change is detected, the QML engine re-evaluates the binding expression and applies the new result to the property.
And the example in https://doc.qt.io/qt-6/qtqml-syntax-propertybinding.html#using-keyword-this-keyword-with-property-binding does say
To bind the Rectangle's height to its own width, the binding expression must explicitly refer to this.width (or alternatively, rect.width):
This does imply to me that QML builds a "graph" for a binding which specifies each base property it references so it knows when to recalculate.
-
@JonB
Thanks Jon. QML I have clarity.This is C++ code binding introduced in Qt 6+. Property binding in C++. This lamda expression confused me. I can write any code inside the lambda. I can refer the variables in side. Do something else etc. So how binding is built with variable which are referred in side the lambda.
-
@dheerendra
Well, I've jumped in here erroneously already so why not stick my head out further?Formulating a Property Binding still talks about
Any C++ expression evaluating to the correct type can be used as a binding expression and be given to the setBinding() method. However, to formulate a correct binding, some rules must be followed.
Dependency tracking only works on bindable properties. It's the developer's responsibility to ensure that all properties used in the binding expression are bindable properties. When non-bindable properties are used in a binding expression, changes to those properties do not trigger updates to the bound property. No warning or error is generated either at compile-time or at run-time. The bound property will be updated only when bindable properties used in the binding expression are changed
So we are still talking about "tracking bindable properties in an expression" to decide whether the expression's value has changed. Per your template <typename Functor> QPropertyBinding<T> QProperty::setBinding(Functor f)
The property's value is set to the result of evaluating the new binding. Whenever a dependency of the binding changes, the binding will be re-evaluated, and the property's value gets updated accordingly.
I think the code really does see that you are accessing other properties and so update. But i really will shut up now, as you probably guessed this too!
OK, finally I think this really does say it: under Qt Bindable Properties
Qt provides bindable properties. Bindable properties are properties which either have a value or are specified using any C++ function, typically a C++ lambda expression. In case they are specified using a C++ function, they are updated automatically whenever their dependencies change.
-
cough cough My voice is not my own:
The setBinding function itself doesn't directly analyze the lambda body or "know" about the variables width and height. Instead, the QProperty class provides a mechanism for registering dependencies between properties.
When you call setBinding, you're essentially telling the QProperty object that the value of this property (area in your case) depends on the values of other properties, namely width and height.
The lambda expression you provide is a function that calculates the value of the area property based on the values of width and height. However, it's not the lambda expression itself that the QProperty object "understands" or keeps track of; it's the fact that you've registered a dependency on width and height.
Internally, when you call setBinding, the QProperty object can inspect the lambda expression and identify any external variables that are captured by reference. In your lambda & { return width * height; }, width and height are captured by reference (&), indicating that they are external variables used inside the lambda body.
Once the QProperty object identifies these external variables, it stores them as dependencies for the property. So, whenever the values of these dependencies change (i.e., when width or height changes), the QProperty object knows that it needs to re-evaluate the binding function associated with the property.
In essence, the QProperty class provides a mechanism for tracking dependencies between properties based on the information you provide when you set up bindings. It doesn't directly analyze the lambda body or "understand" the variables used inside it; it simply keeps track of the dependencies you specify.
-
@J-Hilk said in C++ QProperty binding Question:
Internally, when you call setBinding, the QProperty object can inspect the lambda expression and identify any external variables that are captured by reference.
Once the QProperty object identifies these external variables, it stores them as dependencies for the property.
Exactly! But how does it have access to do that? The C++ lambda is compiled into code by the time it gets passed to
setBinding()
, is it not, so what calls are available to say "what variables/properties are referenced in this compiled C++ expression/functor"? I guess "reflection" somehow, but how does it even see thatwidth
orheight
are in the expression once it's compiled?P.S.
Are these "Qt Bindable Properties" new at Qt6, so I (Qt5) cannot even play with them? -
Thank you. Yes QProperty is doing this. Unless some scanning of lambda happens it cannot achieve this. When does this happen ? How does it happen ? Some code scanning must happen. Is there some preprocessing like Moc. I know moc is not doing.
Any way code works. I have imagined like oh something happens like this. However when what how etc were not clear.
-
@dheerendra Qt is open source you can look in the code to see how this overload of setBinding works
-
@dheerendra
May I suggest you try this.- To your properties add
QProperty<int> dummy { 42 };
- To your lambda add
qDebug() << "area being recalculated";
.
Now try altering the value of
dummy
, only. Does theqDebug()
get called?-
If it does then re-evaluation is not tied to the fact that the lambda only accesses
width
&height
, it looks like a change to any property triggers re-evaluation. I'm kind of hoping it works this way, bad for efficiency, good for understanding! -
If it does not get called then somehow Qt has figured out that the lambda accesses only certain variables/properties, and I'm not sure how it dos that from code.
- To your properties add
-
@J-Hilk said in C++ QProperty binding Question:
Internally, when you call setBinding, the QProperty object can inspect the lambda expression and identify any external variables that are captured by reference. In your lambda & { return width * height; }, width and height are captured by reference (&), indicating that they are external variables used inside the lambda body.
No.
The QProperty knows its dependency because when you call setBinding with a lambda, the lambda is executed and it tracks the other properties that are being evaluated in it.
If both
width
andheight
areQProperty<int>
.
width * height
is first calling the implicitQProperty<int>::operator int()
for both, converting them toint
so it can call the arithmetic operator * on their values. This operator int() is what is registering the dependency.To put it otherwise, when calling
area.setBinding([&](){ return width * height; });
here is what happens:- the lambda is stored in the area QProperty to be called later.
area
is marked as the current property being evaluated.- the lambda is then executed.
width
is being accessed through its conversion operator, it is thus being registered as a dependency of the currently evaluated property (area
here)- the same happens for
height
- the return value of the lambda is assigned to
area
area
is no longer marked as the currently evaluated property.
After that, every time that
width
andheight
are changed, the lambda is being reevaluated and the above process redone. -
@GrecKo said in C++ QProperty binding Question:
width * height is first calling the implicit QProperty<int>::operator int() for both, converting them to int so it can call the arithmetic operator * on their values. This operator
int()
is what is registering the dependency.[My italics.] Wow! OK, that answers the question as to how it gets at the dependencies without "analyzing" code, which it couldn't really do from C++. But that means there must be this or an equivalent for every possible operation you could do on a
QProperty
. Makes you wonder if you find some expression involving aQProperty
which cannot be "tracked" like this, but I guess not. -
@GrecKo said in C++ QProperty binding Question:
the lambda is then executed.
Thanks GrecKo. This clears my thought process. Lambda is getting executed the moment you do the setBinding.
-