Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to pass and invoke lambda function at slot ?



  • Hi,

    I want to take lambda as function parameter and invoke it, but couldn't realize how to do that. For ex:

    MyObject::ask(const QString & text, QObject * receiver, / * param type ? */ func) {
        // setup m_msgBox to ask question
        connect(m_msgBox,  &QMessageBox::finished, this, [&](int result) {
            func(result); // how to call lambda function ?
        });
        m_msgBox->open();
    }
    
    MyObject::someFunction() {
        ask(tr("How?"), this, [&](int result) {
            // process result
        });
    }
    


  • std::function

    MyObject::ask(const QString & text, QObject * receiver, std::function<void(int)> func)
    

    or

    template<class T>
    MyObject::ask(const QString & text, QObject * receiver, T func)
    

    The template one can get messy for class members, but is still doable.



  • @fcarney Thanks.

    I tried to did first way, and call like:

    MyObject::ask(const QString & text, QObject * receiver, std::function<void(int)> func) {
    ...
    func(result);
    

    But failed at functional header:

    __throw_bad_function_call();
    

    Also, should I use receiver ?



  • Why are you passing this here?:

    connect(m_msgBox,  &QMessageBox::finished, this, [&](int result) {
    

    Can you create a minimal project that shows the error? Something we can compile?



  • @fcarney Right, no need to pass this.
    Found the problem, changing the capture from & to = solved the exception.

    #include <QApplication>
    #include <QMessageBox>
    #include <QPushButton>
    #include <QDebug>
    #include <QScreen>
    #include <QStyle>
    
    class Button : public QPushButton
    {
        Q_OBJECT
    public:
        Button(QWidget *parent = nullptr) : QPushButton(parent) {
            setText(tr("Click"));
            m_msgBox = new QMessageBox(this);
            connect(this, &QPushButton::clicked, [&](){
                askProceed();
            });
        }
        virtual ~Button() {}
    
        void ask(
                const QString & text,
                const QStringList & buttons,
                int acceptButton,
                int rejectButton,
                std::function<void(int)> func)
        {
            m_msgBox->setText(text);
            m_msgBox->setIcon(QMessageBox::Question);
            m_msgBox->setStandardButtons(QMessageBox::NoButton);
            QList<QAbstractButton*> buttonList;
            for (int i = 0; i < buttons.count(); ++i) {
                if (i == acceptButton) {
                    QPushButton * btn = m_msgBox->addButton(buttons.at(i), QMessageBox::AcceptRole);
                    m_msgBox->setDefaultButton(btn);
                    buttonList.append((QAbstractButton*)btn);
                }
                else if (i == rejectButton) {
                    QPushButton * btn = m_msgBox->addButton(buttons.at(i), QMessageBox::RejectRole);
                    m_msgBox->setEscapeButton((QAbstractButton*)btn);
                    buttonList.append((QAbstractButton*)btn);
                }
                else {
                    Q_ASSERT(false);
                }
            }
            connect(m_msgBox,  &QMessageBox::finished, [=](int) {
                int res = rejectButton;
                for (int i = 0; i < buttonList.count(); ++i) {
                    if (m_msgBox->clickedButton() == buttonList.at(i)) {
                        res = i;
                        break;
                    }
                }
                QList<QAbstractButton*> listToDelete = m_msgBox->buttons();
                foreach (QAbstractButton * btn, listToDelete) {
                    m_msgBox->removeButton(btn);
                    btn->deleteLater();
                }
                m_msgBox->disconnect();
                func(res);
            });
            m_msgBox->open();
        }
        void askProceed() {
            ask(
                        tr("Which do you prefer?"),
                        QStringList() << tr("Apple") << tr("Pineapple"),
                        0, 1, [&](int result) {
                if (result == 0)
                    qDebug() << "Selected Apple";
                else if (result == 1)
                    qDebug() << "Selected Pineapple";
                else
                    qDebug() << "Unexpected result = " << result;
    
            });
        }
    private:
        QMessageBox * m_msgBox = nullptr;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        Button btn;
        btn.resize(100, 30);
        btn.setGeometry(
            QStyle::alignedRect(
                Qt::LeftToRight,
                Qt::AlignCenter,
                btn.size(),
                qApp->screens().first()->availableGeometry()
            )
        );
        btn.show();
        return a.exec();
    }
    
    #include "main.moc"
    

    Also added to my snippets:
    https://gitlab.com/snippets/1888176



  • @fcarney said in How to pass and invoke lambda function at slot ?:

    Why are you passing this here?:
    connect(m_msgBox, &QMessageBox::finished, this, [&](int result) {

    Should we check if the object and function exists before calling the callback ?



  • @huseyinkozan said in How to pass and invoke lambda function at slot ?:

    Should we check if the object and function exists before calling the callback ?

    When a QObject is deleted, all connection on this object are removed ==> Take a look at QObject::~QObject() for more details



  • @kromignon Thanks.
    Then we can be sure that all childs of deleted QObject will disconnect.

    At my example, m_msgBox will be deleted after btn delete, and lambda for &QMessageBox::finished will not call. I prefer checking func is not a dangling pointer. But seems not possible with std::function::operator==.

    I tried to find at QObject::connect() code at woboq, but couldn't find how to handle lambdas.



  • @huseyinkozan said in How to pass and invoke lambda function at slot ?:

    but couldn't find how to handle lambdas.

    Qt documentation is big, so find the right information is not that easy, but Google is very performant for "data mining".
    A simple search with "connect functor" and you will find Making Connections to Lambda Expressions



  • @kromignon Thanks.
    I had found the page that you gave, but couldn't realise how QObject::connect() accepts lambdas.

    I am using lambdas newly. Just read this article, and helped alot:
    https://blog.feabhas.com/2014/03/demystifying-c-lambdas/





  • @kromignon Thanks.

    I had also read that article, but that also not defines how Qt handle this stuation:

    In this version, we pass monitor as a context to connect(). It won't affect the execution of our lambda, but when monitor is deleted, Qt will notice and will disconnect Worker::progress() from our lambda.

    I tried with below, but could not find how to detect I have already disconnect:

    connect(receiver, &QObject::destroyed, [this](){
        qDebug() << "receiver destroyed";
        m_msgBox->disconnect();
    });
    

    Full code with receiver (deletes btn after 10 sec!):

    #include <QApplication>
    #include <QMessageBox>
    #include <QPushButton>
    #include <QDebug>
    #include <QScreen>
    #include <QStyle>
    #include <QTimer>
    
    class Button : public QPushButton
    {
        Q_OBJECT
    public:
        Button(QWidget *parent = nullptr) : QPushButton(parent) {
            setText(tr("Click"));
            m_msgBox = new QMessageBox(this);
            connect(this, &QPushButton::clicked, [this](){
                ask(tr("Which do you prefer?"),
                    QStringList() << tr("Apple") << tr("Pineapple"),
                    0, 1, this,
                    [](int result) {
                      if (result == 0)
                          qDebug() << "Selected Apple";
                      else if (result == 1)
                          qDebug() << "Selected Pineapple";
                      else
                          qDebug() << "Unexpected result = " << result;
                });
            });
        }
        virtual ~Button() {}
    
        void ask(
                const QString & text,
                const QStringList & buttons,
                int acceptButton,
                int rejectButton,
                const QObject * receiver,
                std::function<void(int)> func)
        {
            m_msgBox->setText(text);
            m_msgBox->setIcon(QMessageBox::Question);
            m_msgBox->setStandardButtons(QMessageBox::NoButton);
            QList<QAbstractButton*> buttonList;
            for (int i = 0; i < buttons.count(); ++i) {
                if (i == acceptButton) {
                    QPushButton * btn = m_msgBox->addButton(buttons.at(i), QMessageBox::AcceptRole);
                    m_msgBox->setDefaultButton(btn);
                    buttonList.append((QAbstractButton*)btn);
                }
                else if (i == rejectButton) {
                    QPushButton * btn = m_msgBox->addButton(buttons.at(i), QMessageBox::RejectRole);
                    m_msgBox->setEscapeButton((QAbstractButton*)btn);
                    buttonList.append((QAbstractButton*)btn);
                }
                else {
                    Q_ASSERT(false);
                }
            }
            connect(m_msgBox,  &QMessageBox::finished, [this, rejectButton, buttonList, func](int) {
                m_msgBox->disconnect();
                int res = rejectButton;
                for (int i = 0; i < buttonList.count(); ++i) {
                    if (m_msgBox->clickedButton() == buttonList.at(i)) {
                        res = i;
                        break;
                    }
                }
                QList<QAbstractButton*> listToDelete = m_msgBox->buttons();
                foreach (QAbstractButton * btn, listToDelete) {
                    m_msgBox->removeButton(btn);
                    btn->deleteLater();
                }
                func(res);
            });
            connect(receiver, &QObject::destroyed, [this](){
                qDebug() << "receiver destroyed";
                m_msgBox->disconnect();
            });
            m_msgBox->open();
        }
    private:
        QMessageBox * m_msgBox = nullptr;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        Button * btn = new Button;
        btn->resize(100, 30);
        btn->setGeometry(
            QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, btn->size(),
                                qApp->screens().first()->availableGeometry()));
        btn->show();
        QTimer::singleShot(10000, [&](){
            delete btn;
            btn = nullptr;
        });
        int res = a.exec();
        if (btn)
            delete btn;
        return res;
    }
    
    #include "main.moc"
    

Log in to reply