Combining Qt’s Signals and Slots with c++0x lamdas

Qt is a fantastically designed library. However, every now and then I think of something that I wish they offered that they don't. It's almost always something small and easily worked around, but it would be nice if it were just there. This time around, that feature is the ability to connect a signal to a function that is not a member of a class/struct. Specifically, I think it would be really cool if I could connect it to a c++0x lambda! Especially now that the ISO C++ committee approved the C++0x final draft.


Fortunately, this turned out to be fairly easy. Firstly we need an object to receive the signal since Qt mandates that all slots are member functions. Seems easy enough, so we'll start with that:

class connect_functor_helper : public QObject {
    Q_OBJECT
public:
    // TBD
};

And we of course need a function to do the connection, we'll template it so it can accept anything, a function, a functor, or even a lambda :-).

template <class T>
bool connect(QObject *sender, const char *signal, const T &reciever, Qt::ConnectionType type = Qt::AutoConnection) {
    // TBD
}

The pieces are all here, all we have to do is put them together. I do wish that MOC supported templated types, which would make this slightly easier (since we want to allow anything to be connected to). So I ended up resorting to using boost::function. When we fill in the blanks, the class looks like this:

class connect_functor_helper : public QObject {
    Q_OBJECT
public:
    connect_functor_helper(QObject *parent, const boost::function<void()> &f) : QObject(parent), function_(f) {
    }

public Q_SLOTS:
    void signaled() {
        function_();
    }

private:
    boost::function<void()> function_;
};

So two things are happening here that are noteworthy.

  • We make the helper object a child of some QObject (ideally the sender of the signal) so that it will be automatically deleted when the parent object gets destroyed.
  • We have a simple boost::function to store the function we want and call it from a slot called signaled(). We use this because it allows us to be generic without resorting to templates. The last thing is to just add the connection, which is easy enough :-).
template <class T>
bool connect(QObject *sender, const char *signal, const T &reciever, Qt::ConnectionType type = Qt::AutoConnection) {
    return QObject::connect(sender, signal, new connect_functor_helper(sender, reciever), SLOT(signaled()), type);
}

Huzzah! We now can connect a Qt signal to anything that is callable like a function with the prototype void f(), even lambdas! Of course, this can be expanded to deal with parameters as well, this is just the tip of the iceberg. So once we put all of the above into a header, we finally have the following demonstration program which works when QMAKE_CXXFLAGS contains --std=c++0x:

#include <QApplication>
#include <QPushButton>
#include <iostream>
#include "LambdaConnect.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QPushButton w("Push Me");
    w.show();
    connect(&w, SIGNAL(clicked()), []{
        std::cout << "clicked" << std::endl;
    });
    app.exec();
}

Enjoy!