Passing variable-length args in container for QMetaObject::invokeMethod
-
I have a use case where I'm using QMetaObject::invokeMethod to call any of a variety of slot functions within my application which require various numbers of function parameters. What I'd like to do is as follows:
QList<QMetaMethodArgument> args; for (int i = 0; i < num_args_required; i++){ args.append(arg); } QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,args);
This doesn't work because I suspect that invokeMethod is treating the container itself as the arg, thus not matching any of my target function definitions. Indeed, no errors occur but my functions do not get called.
Is there a way to do this? If not, I'll have to use a cumbersome, pyramid-like case statement similar to this to pass them discretely:
switch(num_args_required){ case 1: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1); break; case 2: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1,arg2); break; case 3: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1,arg2,arg3); break; // etc... up to 10 params }
Should also mention that the way this was implemented in Qt5.15 was that, because 10 params was the maximum amount, I just always passed in 10 params with the unused ones being set to empty QGenericArgument objects. Doing this in Qt6.5 with QMetaMethodArgument seg faults.
-
I have a use case where I'm using QMetaObject::invokeMethod to call any of a variety of slot functions within my application which require various numbers of function parameters. What I'd like to do is as follows:
QList<QMetaMethodArgument> args; for (int i = 0; i < num_args_required; i++){ args.append(arg); } QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,args);
This doesn't work because I suspect that invokeMethod is treating the container itself as the arg, thus not matching any of my target function definitions. Indeed, no errors occur but my functions do not get called.
Is there a way to do this? If not, I'll have to use a cumbersome, pyramid-like case statement similar to this to pass them discretely:
switch(num_args_required){ case 1: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1); break; case 2: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1,arg2); break; case 3: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1,arg2,arg3); break; // etc... up to 10 params }
Should also mention that the way this was implemented in Qt5.15 was that, because 10 params was the maximum amount, I just always passed in 10 params with the unused ones being set to empty QGenericArgument objects. Doing this in Qt6.5 with QMetaMethodArgument seg faults.
Hi and welcome to devnet,
You might be able to do something using tuples and some template black magic but if you really have the need of ten or more parameters, you really should consider using a dedicated struct or class to pass them to your slot.
Note: I insiste highly on might and I would not recommend it.
-
Hi and welcome to devnet,
You might be able to do something using tuples and some template black magic but if you really have the need of ten or more parameters, you really should consider using a dedicated struct or class to pass them to your slot.
Note: I insiste highly on might and I would not recommend it.
@SGaist Thanks, I appreciate the response.
Not sure offhand whether there's enough commonality between all the slots in my application to make an unbloated struct, but I agree that's a promising way to circumvent the problem. Bummer that I can't just use a container object directly.
-
I have a use case where I'm using QMetaObject::invokeMethod to call any of a variety of slot functions within my application which require various numbers of function parameters. What I'd like to do is as follows:
QList<QMetaMethodArgument> args; for (int i = 0; i < num_args_required; i++){ args.append(arg); } QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,args);
This doesn't work because I suspect that invokeMethod is treating the container itself as the arg, thus not matching any of my target function definitions. Indeed, no errors occur but my functions do not get called.
Is there a way to do this? If not, I'll have to use a cumbersome, pyramid-like case statement similar to this to pass them discretely:
switch(num_args_required){ case 1: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1); break; case 2: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1,arg2); break; case 3: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1,arg2,arg3); break; // etc... up to 10 params }
Should also mention that the way this was implemented in Qt5.15 was that, because 10 params was the maximum amount, I just always passed in 10 params with the unused ones being set to empty QGenericArgument objects. Doing this in Qt6.5 with QMetaMethodArgument seg faults.
I wasn't quite able to follow what exactly is required, but is it this?
template <typename ... Args> auto methodFor(QObject * target, const char * action) { return [context = QPointer<QObject>(target), slot = QByteArray(action)] (Args && ... args) -> void { if (!context) return; QMetaObject::invokeMethod(context.data(), slot.data(), Qt::QueuedConnection, QArgument<Args>(QMetaType::fromType<Args>().name(), std::forward<Args>(args))...); }; }
Usage (basically wraps the call, the name of the slot and the object in a free function):
auto noArgs = methodFor(this, "someMethod"); auto twoArgs = methodFor<int, float>(this, "anotherMethod");
Then called like:
if (foo) noArgs(); else twoArgs(0, 1.2f);
-
I wasn't quite able to follow what exactly is required, but is it this?
template <typename ... Args> auto methodFor(QObject * target, const char * action) { return [context = QPointer<QObject>(target), slot = QByteArray(action)] (Args && ... args) -> void { if (!context) return; QMetaObject::invokeMethod(context.data(), slot.data(), Qt::QueuedConnection, QArgument<Args>(QMetaType::fromType<Args>().name(), std::forward<Args>(args))...); }; }
Usage (basically wraps the call, the name of the slot and the object in a free function):
auto noArgs = methodFor(this, "someMethod"); auto twoArgs = methodFor<int, float>(this, "anotherMethod");
Then called like:
if (foo) noArgs(); else twoArgs(0, 1.2f);
@kshegunov Your response answers my question of whether or not that it's technically possible to pass a variable number of args to a single invokeMethod() call (it is). However, your solution doesn't avoid the problem of a pyramid case statement and adds bloat for the template method abstraction:
auto oneArgs = methodFor<int>(this, "oneArgSlot"); auto twoArgs = methodFor<int, int>(this, "twoArgsSlot"); auto threeArgs = methodFor<int, int, int>(this, "threeArgsSlot"); // ...and more
then:
switch(num_args_required){ case 1: oneArgs(arg1); break; case 2: twoArgs(arg1,arg2); break; case 3: threeArgs(arg1,arg2,arg3); break; // ...and more }
-
@kshegunov Your response answers my question of whether or not that it's technically possible to pass a variable number of args to a single invokeMethod() call (it is). However, your solution doesn't avoid the problem of a pyramid case statement and adds bloat for the template method abstraction:
auto oneArgs = methodFor<int>(this, "oneArgSlot"); auto twoArgs = methodFor<int, int>(this, "twoArgsSlot"); auto threeArgs = methodFor<int, int, int>(this, "threeArgsSlot"); // ...and more
then:
switch(num_args_required){ case 1: oneArgs(arg1); break; case 2: twoArgs(arg1,arg2); break; case 3: threeArgs(arg1,arg2,arg3); break; // ...and more }
@flatland3r said in Passing variable-length args in container for QMetaObject::invokeMethod:
However, your solution doesn't avoid the problem of a pyramid case statement
Let me try to answer this with a question. It's all fine and cool when we use pseudo code, but how
num_args_required
is determined? Is it supposed to be deduced by a size of an array/list? What if the array doesn't contain the correct types (e.g.QVariantList
)? You do realize Qt is not a language, and C++ is statically typed, right?If you can break down the problem, to something that is concrete, maybe I could be able to help. At least to me, it seems you're describing how
va_start
/va_arg
/va_end
operate.and adds bloat for the template method abstraction:
In this particular case it's completely irrelevant, because
- Bloat is a relative term,
QMetaObject::invokeMethod
is already a bloat and expanding a single template function isn't really very bloat-y. - It's compile-time bloat, which even if annoying is irrelevant (with exceptions).
- Bloat is a relative term,
-
J JonB referenced this topic on
-
I have a use case where I'm using QMetaObject::invokeMethod to call any of a variety of slot functions within my application which require various numbers of function parameters. What I'd like to do is as follows:
QList<QMetaMethodArgument> args; for (int i = 0; i < num_args_required; i++){ args.append(arg); } QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,args);
This doesn't work because I suspect that invokeMethod is treating the container itself as the arg, thus not matching any of my target function definitions. Indeed, no errors occur but my functions do not get called.
Is there a way to do this? If not, I'll have to use a cumbersome, pyramid-like case statement similar to this to pass them discretely:
switch(num_args_required){ case 1: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1); break; case 2: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1,arg2); break; case 3: QMetaObject::invokeMethod(target,action,Qt::QueuedConnection,arg1,arg2,arg3); break; // etc... up to 10 params }
Should also mention that the way this was implemented in Qt5.15 was that, because 10 params was the maximum amount, I just always passed in 10 params with the unused ones being set to empty QGenericArgument objects. Doing this in Qt6.5 with QMetaMethodArgument seg faults.
@flatland3r what worked for me is code listed on https://forum.qt.io/topic/145618/qmetamethod-how-to-use-after-the-changes
basically always passing in 10 arguments and Qt would ignore the ones that were unneeded.
I don't know how to do this anymore since the QMeta stuff has replaced the QGeneric* classes. Which I filed a bugreport about on QTBUG-114362
-
@flatland3r what worked for me is code listed on https://forum.qt.io/topic/145618/qmetamethod-how-to-use-after-the-changes
basically always passing in 10 arguments and Qt would ignore the ones that were unneeded.
I don't know how to do this anymore since the QMeta stuff has replaced the QGeneric* classes. Which I filed a bugreport about on QTBUG-114362
ok, I know how to do this but its really really ugly.
It essentially is 2 sets of 10 calls to invoke in my sourcecode and some logic to decide which of the invoke() methods I actually call.
I have 10 calls based on the amount of arguments there are. So, Qt is awesome it deprecated the methods only to force me to write 10 of them..
Then the invoke returns false if you pass in a default-constructed QMetaMethodReturnArgument which indicates 'void'.
No, you have to call it without that argument if the method returns void. So... Yeah, another set of 10 calls..I'm hoping I missed something, because this is beneath Qt