c++qtqt5qmetaobjectqt-slot

QMetaMethod::invoke() with argument defaults


When calling QMetaMethod::invoke() on a method that contains default arguments, the invoke fails.

class MyClass : public QObject
{
    Q_OBJECT
public:
    Q_INVOKABLE MyClass() : QObject(nullptr){}

public slots:
    int MyMethod(int a = 0)
    {
        return a*2;
    }
};


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyClass* object = new MyClass();
    QMetaObject *metaObject = object->metaObject();

    for(int i=metaObject->methodOffset(); i<metaObject->methodCount(); i++)
    {
        if(metaObject->method(i).name() == "MyMethod")
        {
            int returnVal;

            //returns false
            metaObject->method(i).invoke(object,
                                         Qt::DirectConnection,
                                         Q_RETURN_ARG(int, returnVal));

            break;
        }
    }

    return a.exec();
}

If I pass an int as the first argument, then it runs fine. Is there any way to retrieve the default values of the arguments for the method so that way I can pass those instead of passing nothing?

I was about to manually store the defaults within the class for each method, but this is an ugly hack.

Thanks for your time.


Solution

  • If you review the generated .moc you see the following:

    void MyClass::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
    {
        if (_c == QMetaObject::InvokeMetaMethod) {
            MyClass *_t = static_cast<MyClass *>(_o);
            Q_UNUSED(_t)
            switch (_id) {
            case 0: { int _r = _t->MyMethod((*reinterpret_cast< int(*)>(_a[1])));
                if (_a[0]) *reinterpret_cast< int*>(_a[0]) = std::move(_r); }  break;
            case 1: { int _r = _t->MyMethod();
                if (_a[0]) *reinterpret_cast< int*>(_a[0]) = std::move(_r); }  break;
            default: ;
            }
        }
    }
    

    As you can see there are 2 methods generated and that can be verified by printing the methods with that name:

    #include <QCoreApplication>
    #include <QMetaMethod>
    #include <QDebug>
    
    class MyClass: public QObject
    {
        Q_OBJECT
    public:
        using QObject::QObject;
    public slots:
        int MyMethod(int a = 0){ return a*2;}
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        MyClass object;
        const QMetaObject *metaObject = object.metaObject();
        for(int i=metaObject->methodOffset(); i<metaObject->methodCount(); i++)
        {
            QMetaMethod method = metaObject->method(i);
            if(method.name() == QByteArray("MyMethod"))
                qDebug()<<i<<method.name();
        };
        return 0;
    }
    
    #include "main.moc"
    

    Output:

    5 "MyMethod"
    6 "MyMethod"
    

    So what sets them apart? The number of parameters, so you must add a filter that is the parameterCount().

    #include <QCoreApplication>
    #include <QMetaMethod>
    #include <QDebug>
    
    class MyClass: public QObject
    {
        Q_OBJECT
    public:
        using QObject::QObject;
    public slots:
        int MyMethod(int a = 0){ return a*2;}
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        MyClass object;
        const QMetaObject *metaObject = object.metaObject();
        for(int i=metaObject->methodOffset(); i<metaObject->methodCount(); i++)
        {
            QMetaMethod method = metaObject->method(i);
            if(method.name() == QByteArray("MyMethod") && method.parameterCount() == 0)
            {
                int returnVal;
                bool status = method.invoke(&object,
                                            Qt::DirectConnection,
                                            Q_RETURN_ARG(int, returnVal));
                Q_ASSERT(status);
                qDebug()<<returnVal;
            }
        };
        return 0;
    }
    
    #include "main.moc"
    

    Output:

    0
    

    On the other hand if you want to avoid this kind of problems you can use QMetaObject::invokeMethod() that makes that verification:

    MyClass object;
    int returnVal;
    bool status = QMetaObject::invokeMethod(&object,
                                            "MyMethod",
                                            Qt::DirectConnection,
                                            Q_RETURN_ARG(int, returnVal));
    Q_ASSERT(status);
    qDebug()<<returnVal;