passing arguments to C++ functions
-
wrote on 8 Dec 2023, 16:49 last edited by
Hi all -
I'm not sure whether this is a QML question or a C++ question, but I figured I start here.
I have a C++ model that uses a list of pointers to objects. These objects may be members of a parent class (Equipment) or of a subclass (eg Vsp).
I need to perform some activities that are more appropriately performed in C++ than in QML/JS. My problem is, when I pass an argument that is a subclass, I don't know how to get my C++ function to recognize that it's a subclass. A QML call might look like:
ColumnLayout { property vspStruct vsp // vspStruct is the subclass ... onButtonClicked: { if (equipmentModel.changesExist(vsp)) { ...
And the C++ routine is:
bool EquipmentModel::changesExist(Equipment e) { ...
As you can see, this isn't going to work, because the argument is treated as a member of the parent class, but I need to look at properties of the subclass.
So...is there something that can be done on the QML side where I can pass this a pointer or a reference or something that would allow me to cast it in the C++ function?
Any other ideas are equally welcome. Thanks...
-
So, after a little trial and error, here's where I stand:
- according to @Axel-Spoerl, since my Equipment class isn't derived from QObject, QML is limited to passing it as a const reference to C++ functions.
- There are (at least) 3 options for coding my C++ function:
- bool EquipmentModel::changesExist(Equipment e)
- bool EquipmentModel::changesExist(const Equipment *e)
- bool EquipmentModel::changesExist(const Equipment &e)
As @Christian-Ehrlicher pointed out, the first option won't work for me. The second option produces a run time error about incompatible JS arguments. The last option builds and runs, but the behavior is somewhat baffling:
My cast doesn't seem to have had the desired effect of "recapturing" the subclass. Perhaps I'm doing something wrong with this?
- Interestingly enough, if I do a direct JS comparison in my QML instead of calling this routine, it works. So, if my assumptions are correct, it's really the QML-to-C++ interface that is the limitation here, and this is probably due to the fact that I'm trying to use a non-QObject based struct.
Unless someone has any QML-based ideas on this, I'm going to consider the matter closed. I do have more questions, but they're more appropriate for the C++ forum.
Thanks to everyone who helped with this.
wrote on 11 Dec 2023, 15:47 last edited by@mzimmers said in passing arguments to C++ functions:
bool EquipmentModel::changesExist(const Equipment &e)
That is the only correct one of the three.
If you want you can discuss elsewhere whether/how to achieve what you want.
-
Hi all -
I'm not sure whether this is a QML question or a C++ question, but I figured I start here.
I have a C++ model that uses a list of pointers to objects. These objects may be members of a parent class (Equipment) or of a subclass (eg Vsp).
I need to perform some activities that are more appropriately performed in C++ than in QML/JS. My problem is, when I pass an argument that is a subclass, I don't know how to get my C++ function to recognize that it's a subclass. A QML call might look like:
ColumnLayout { property vspStruct vsp // vspStruct is the subclass ... onButtonClicked: { if (equipmentModel.changesExist(vsp)) { ...
And the C++ routine is:
bool EquipmentModel::changesExist(Equipment e) { ...
As you can see, this isn't going to work, because the argument is treated as a member of the parent class, but I need to look at properties of the subclass.
So...is there something that can be done on the QML side where I can pass this a pointer or a reference or something that would allow me to cast it in the C++ function?
Any other ideas are equally welcome. Thanks...
Moderatorswrote on 8 Dec 2023, 20:43 last edited by Axel Spoerl 12 Sept 2023, 10:19Edit: non
QObjects
are passed as const ref, not as a (copied) value.@mzimmers
Just to make sure that I get it right:vspStruct
inherits fromEquipment
.changesExist()
takes anEquipment
argument.- in order to do what it has to do,
changesExist()
needs to cast the argument back tovspStruct
and inspect its properties (if the cast is successful).
QML passes a pointer as an argument, if the type inherits from
QObject
, and a const ref otherwise.
If you makeEquipment
a quick item andvspStruct
inherits from it, you can change the signature tochangesExist(Equipment *e)
.Then you can do something like:
if (auto *vsp = qobject_cast<vspStruct *>(e)) return vsp.hasHadSomethingLikeAchange(); return false;
-
Edit: non
QObjects
are passed as const ref, not as a (copied) value.@mzimmers
Just to make sure that I get it right:vspStruct
inherits fromEquipment
.changesExist()
takes anEquipment
argument.- in order to do what it has to do,
changesExist()
needs to cast the argument back tovspStruct
and inspect its properties (if the cast is successful).
QML passes a pointer as an argument, if the type inherits from
QObject
, and a const ref otherwise.
If you makeEquipment
a quick item andvspStruct
inherits from it, you can change the signature tochangesExist(Equipment *e)
.Then you can do something like:
if (auto *vsp = qobject_cast<vspStruct *>(e)) return vsp.hasHadSomethingLikeAchange(); return false;
wrote on 8 Dec 2023, 21:46 last edited by mzimmers 12 Aug 2023, 21:53@Axel-Spoerl yes, your assumptions are all correct.
Equipment is a Q_GADGET, not a Q_OBJECT. Would this approach still work?
I'm not sure what you mean by "Equipment a quick item." Do you mean derive from the QQuickItem class? I don't think I can do that, as this is a Q_GADGET. Using Q_OBJECT is going to be problematic.
Thanks...
-
@Axel-Spoerl yes, your assumptions are all correct.
Equipment is a Q_GADGET, not a Q_OBJECT. Would this approach still work?
I'm not sure what you mean by "Equipment a quick item." Do you mean derive from the QQuickItem class? I don't think I can do that, as this is a Q_GADGET. Using Q_OBJECT is going to be problematic.
Thanks...
@mzimmers
Not entirely sure, if the Q_OBJECT macro, or inheriting fromQObject
does the trick, but I suspect it's only. Bymake Equipment a quick item
I meant, inheriting from quick item.
-
@mzimmers
Not entirely sure, if the Q_OBJECT macro, or inheriting fromQObject
does the trick, but I suspect it's only. Bymake Equipment a quick item
I meant, inheriting from quick item.
wrote on 9 Dec 2023, 00:24 last edited by@Axel-Spoerl I tried it:
struct Equipment : public QQuickItem { Q_GADGET QML_STRUCTURED_VALUE QML_VALUE_TYPE(equipment)
Is this what you meant?
I get compile-time errors about missing functions (probably the copy constructor and destructor). This is why I used Q_GADGET instead.
-
@Axel-Spoerl I tried it:
struct Equipment : public QQuickItem { Q_GADGET QML_STRUCTURED_VALUE QML_VALUE_TYPE(equipment)
Is this what you meant?
I get compile-time errors about missing functions (probably the copy constructor and destructor). This is why I used Q_GADGET instead.
wrote on 9 Dec 2023, 08:18 last edited by JonB 12 Sept 2023, 10:24@mzimmers
A couple of observations from me. Remember I know nothing about QML, only about C++ in general.First, like @Axel-Spoerl said the first approach which comes to mind is
qobject_cast<>
ordynamic_cast<>
. The former can only only be used on a pointer. The latter, however, as discussed in a recent thread on this forum can also be used on a non-pointer value type, though I was not aware of this until that thread.So if your issue is over it being passed as a
Equipment e
rather than avspStruct vsp
you can still godynamic_cast<vspStruct>(e)
(note no pointer). The only "wrinkle" is that while on a pointer the dynamic cast can returnnullptr
if it is not of the required type, which is easy to test for, in the latter, value type case there is no return value possible to indicate this. So instead C++ raises a runtime exception,std::bad_cast
, in such a case. To detect this you would have to wrap the call in a C++try ... catch
, but that is doable.Next, untested but I don't see why you cannot take the object's address to produce a pointer. So
dynamic_cast<vspStruct *>(&e)
should be usable, and would avoid needing to test for an exception as it can returnnullptr
.Finally, since you are in charge of the code for
Equipment
&vspStruct
(right?) have you considered introducing somevirtual
methods (even helper function if useful), wherevspStruct
and other subclassesoverride
them, for your convenience? When you call a virtual method on an object it executes the overridden definition for the actual instance specific-type, even if the code only has a variable of the base type. So in your case with formal parameterEquipment e
callinge.virtualMethod()
will executevspStruct.virtualMethod()
if actual parameter fore
is avspStruct
instance. The advantage here is that you do not have to do any casting ofEquipment
tovspStruct
or know whether the instance passed is some specific sub-classed instance. This may be useful to bear in mind whatever your solution.[I stand to be corrected by a C++ expert if anything I have said here is incorrect, especially about passing a derived type as a value type rather than as a pointer.]
[EDIT After conversations with C++ expert @Christian-Ehrlicher, your attempt to pass a
vspStruct
instance actual parameter to a function receiving anEquipment e
formal parameter, and anything I said relying on that, is incorrect/unusable. That code must make a copy (because neither pointer not reference) but would only callEquipment
's copy constructor, notvspStruct
s. Which means what you receive is no longer avspStruct
and so nothing can make it one or allow you to call/access anything invspStruct
on it. See how @Christian-Ehrlicher has confirmed this in his latest answer later on below.] -
@mzimmers
A couple of observations from me. Remember I know nothing about QML, only about C++ in general.First, like @Axel-Spoerl said the first approach which comes to mind is
qobject_cast<>
ordynamic_cast<>
. The former can only only be used on a pointer. The latter, however, as discussed in a recent thread on this forum can also be used on a non-pointer value type, though I was not aware of this until that thread.So if your issue is over it being passed as a
Equipment e
rather than avspStruct vsp
you can still godynamic_cast<vspStruct>(e)
(note no pointer). The only "wrinkle" is that while on a pointer the dynamic cast can returnnullptr
if it is not of the required type, which is easy to test for, in the latter, value type case there is no return value possible to indicate this. So instead C++ raises a runtime exception,std::bad_cast
, in such a case. To detect this you would have to wrap the call in a C++try ... catch
, but that is doable.Next, untested but I don't see why you cannot take the object's address to produce a pointer. So
dynamic_cast<vspStruct *>(&e)
should be usable, and would avoid needing to test for an exception as it can returnnullptr
.Finally, since you are in charge of the code for
Equipment
&vspStruct
(right?) have you considered introducing somevirtual
methods (even helper function if useful), wherevspStruct
and other subclassesoverride
them, for your convenience? When you call a virtual method on an object it executes the overridden definition for the actual instance specific-type, even if the code only has a variable of the base type. So in your case with formal parameterEquipment e
callinge.virtualMethod()
will executevspStruct.virtualMethod()
if actual parameter fore
is avspStruct
instance. The advantage here is that you do not have to do any casting ofEquipment
tovspStruct
or know whether the instance passed is some specific sub-classed instance. This may be useful to bear in mind whatever your solution.[I stand to be corrected by a C++ expert if anything I have said here is incorrect, especially about passing a derived type as a value type rather than as a pointer.]
[EDIT After conversations with C++ expert @Christian-Ehrlicher, your attempt to pass a
vspStruct
instance actual parameter to a function receiving anEquipment e
formal parameter, and anything I said relying on that, is incorrect/unusable. That code must make a copy (because neither pointer not reference) but would only callEquipment
's copy constructor, notvspStruct
s. Which means what you receive is no longer avspStruct
and so nothing can make it one or allow you to call/access anything invspStruct
on it. See how @Christian-Ehrlicher has confirmed this in his latest answer later on below.]Thank you @JonB, for looking into the issue so much deeper than I did yesterday evening! I actually like the virtual method approach, which could be used to identify if an
Equipment
is actually avspStruct
and hence, casting would work. It could even be used, to make the casting redundant at all. -
Hi all -
I'm not sure whether this is a QML question or a C++ question, but I figured I start here.
I have a C++ model that uses a list of pointers to objects. These objects may be members of a parent class (Equipment) or of a subclass (eg Vsp).
I need to perform some activities that are more appropriately performed in C++ than in QML/JS. My problem is, when I pass an argument that is a subclass, I don't know how to get my C++ function to recognize that it's a subclass. A QML call might look like:
ColumnLayout { property vspStruct vsp // vspStruct is the subclass ... onButtonClicked: { if (equipmentModel.changesExist(vsp)) { ...
And the C++ routine is:
bool EquipmentModel::changesExist(Equipment e) { ...
As you can see, this isn't going to work, because the argument is treated as a member of the parent class, but I need to look at properties of the subclass.
So...is there something that can be done on the QML side where I can pass this a pointer or a reference or something that would allow me to cast it in the C++ function?
Any other ideas are equally welcome. Thanks...
@mzimmers said in passing arguments to C++ functions:
bool EquipmentModel::changesExist(Equipment e) {
This is creating a copy by calling the Equipment copy ctor so no chance to do any cast to vspStruct.
Pass it by const ref or pointer - everything else will not work. -
@mzimmers said in passing arguments to C++ functions:
bool EquipmentModel::changesExist(Equipment e) {
This is creating a copy by calling the Equipment copy ctor so no chance to do any cast to vspStruct.
Pass it by const ref or pointer - everything else will not work.wrote on 9 Dec 2023, 10:25 last edited by@Christian-Ehrlicher I have appended a new paragraph to my earlier response as a result of this conversation with you.
-
@Christian-Ehrlicher I have appended a new paragraph to my earlier response as a result of this conversation with you.
wrote on 9 Dec 2023, 14:08 last edited by@JonB thanks for the in-depth response. I've actually tried the virtual method approach you suggest, but I must be doing something wrong:
// equipment.h virtual QVariant data(int role); // vsp.h QVariant data(int role) override; // equipmentModel.cpp bool EquipmentModel::changesExist(Equipment e) { e.data(256); // calls the method in equipment.h, not vsp.h. ...
BTW: my equipment struct isn't abstract; I actually use it for some items. I'm not sure whether this makes a difference.)
-
@JonB thanks for the in-depth response. I've actually tried the virtual method approach you suggest, but I must be doing something wrong:
// equipment.h virtual QVariant data(int role); // vsp.h QVariant data(int role) override; // equipmentModel.cpp bool EquipmentModel::changesExist(Equipment e) { e.data(256); // calls the method in equipment.h, not vsp.h. ...
BTW: my equipment struct isn't abstract; I actually use it for some items. I'm not sure whether this makes a difference.)
wrote on 9 Dec 2023, 15:27 last edited by JonB 12 Sept 2023, 15:34@mzimmers
Please re-read the discussion. The point is that if you have a formal parameterEquipment e
this has to be passed in as a copy by value to a temporary of base-typeEquipment
. You pass avspStruct
as your parameter, but that does not get through to the function, it gets just anEquipment
copy. And soe.data()
isEquipment::data()
, thevspStruct
original has got lost.This would not happen if your method were either of
bool EquipmentModel::changesExist([const] Equipment *e) // pointer bool EquipmentModel::changesExist([const] Equipment &e) // reference
Then it would pass and receive your original
vspStruct
. You need to change over to one of these, notEquipment e
. I believe @Axel-Spoerl & @Christian-Ehrlicher have said QML does not prevent you from doing so. -
@mzimmers
Please re-read the discussion. The point is that if you have a formal parameterEquipment e
this has to be passed in as a copy by value to a temporary of base-typeEquipment
. You pass avspStruct
as your parameter, but that does not get through to the function, it gets just anEquipment
copy. And soe.data()
isEquipment::data()
, thevspStruct
original has got lost.This would not happen if your method were either of
bool EquipmentModel::changesExist([const] Equipment *e) // pointer bool EquipmentModel::changesExist([const] Equipment &e) // reference
Then it would pass and receive your original
vspStruct
. You need to change over to one of these, notEquipment e
. I believe @Axel-Spoerl & @Christian-Ehrlicher have said QML does not prevent you from doing so. -
@mzimmers said in passing arguments to C++ functions:
bool EquipmentModel::changesExist(Equipment e) {
This is creating a copy by calling the Equipment copy ctor so no chance to do any cast to vspStruct.
Pass it by const ref or pointer - everything else will not work.wrote on 9 Dec 2023, 15:34 last edited by@Christian-Ehrlicher said in passing arguments to C++ functions:
This is creating a copy by calling the Equipment copy ctor so no chance to do any cast to vspStruct.
Pass it by const ref or pointer - everything else will not work. -
wrote on 9 Dec 2023, 15:45 last edited by mzimmers 12 Sept 2023, 15:45
@JonB said in passing arguments to C++ functions:
You need to change over to one of these, not Equipment e. I believe @Axel-Spoerl & @Christian-Ehrlicher have said QML does not prevent you from doing so.
Well, I tried that, and this is the result:
Notice that e is still an Equipment. By contrast, listEntry is a (pointer to a) Vsp.Or, am I still not understanding you?
-
@JonB said in passing arguments to C++ functions:
You need to change over to one of these, not Equipment e. I believe @Axel-Spoerl & @Christian-Ehrlicher have said QML does not prevent you from doing so.
Well, I tried that, and this is the result:
Notice that e is still an Equipment. By contrast, listEntry is a (pointer to a) Vsp.Or, am I still not understanding you?
wrote on 11 Dec 2023, 15:17 last edited bySo, after a little trial and error, here's where I stand:
- according to @Axel-Spoerl, since my Equipment class isn't derived from QObject, QML is limited to passing it as a const reference to C++ functions.
- There are (at least) 3 options for coding my C++ function:
- bool EquipmentModel::changesExist(Equipment e)
- bool EquipmentModel::changesExist(const Equipment *e)
- bool EquipmentModel::changesExist(const Equipment &e)
As @Christian-Ehrlicher pointed out, the first option won't work for me. The second option produces a run time error about incompatible JS arguments. The last option builds and runs, but the behavior is somewhat baffling:
My cast doesn't seem to have had the desired effect of "recapturing" the subclass. Perhaps I'm doing something wrong with this?
- Interestingly enough, if I do a direct JS comparison in my QML instead of calling this routine, it works. So, if my assumptions are correct, it's really the QML-to-C++ interface that is the limitation here, and this is probably due to the fact that I'm trying to use a non-QObject based struct.
Unless someone has any QML-based ideas on this, I'm going to consider the matter closed. I do have more questions, but they're more appropriate for the C++ forum.
Thanks to everyone who helped with this.
-
So, after a little trial and error, here's where I stand:
- according to @Axel-Spoerl, since my Equipment class isn't derived from QObject, QML is limited to passing it as a const reference to C++ functions.
- There are (at least) 3 options for coding my C++ function:
- bool EquipmentModel::changesExist(Equipment e)
- bool EquipmentModel::changesExist(const Equipment *e)
- bool EquipmentModel::changesExist(const Equipment &e)
As @Christian-Ehrlicher pointed out, the first option won't work for me. The second option produces a run time error about incompatible JS arguments. The last option builds and runs, but the behavior is somewhat baffling:
My cast doesn't seem to have had the desired effect of "recapturing" the subclass. Perhaps I'm doing something wrong with this?
- Interestingly enough, if I do a direct JS comparison in my QML instead of calling this routine, it works. So, if my assumptions are correct, it's really the QML-to-C++ interface that is the limitation here, and this is probably due to the fact that I'm trying to use a non-QObject based struct.
Unless someone has any QML-based ideas on this, I'm going to consider the matter closed. I do have more questions, but they're more appropriate for the C++ forum.
Thanks to everyone who helped with this.
wrote on 11 Dec 2023, 15:47 last edited by@mzimmers said in passing arguments to C++ functions:
bool EquipmentModel::changesExist(const Equipment &e)
That is the only correct one of the three.
If you want you can discuss elsewhere whether/how to achieve what you want.
-
@mzimmers said in passing arguments to C++ functions:
bool EquipmentModel::changesExist(const Equipment &e)
That is the only correct one of the three.
If you want you can discuss elsewhere whether/how to achieve what you want.
wrote on 11 Dec 2023, 16:37 last edited by@JonB if I understood @Axel-Spoerl correctly, the pointer version would work if I derived my struct from QQuickItem, but that's not a desired option at this time.
And yes, I think it's time to move this discussion to the C++ forum. Thanks again for the help.
-
1/17