Silmor . de
Site Links:
Impressum / Publisher

Lambda Expressions and Qt 4.x

C++ 11 is out and support is building up for the various compilers. Already programmers are starting to use the features and one of the favorites seems to be lambda expressions.

Hint: as of early 2012 some of this code may not work with your favorite compiler, since support is still quite incomplete. E.g. MSVC 2010 will deny the sort code, LLVM 2.9 will not compile lambdas at all. This code has been tested with GCC 4.5 (Mac Users: please complain to Apple!).

Hint 2: as of early 2013 most of this code should work with GCC 4.7, LLVM 3.1, and MSVC 2012. It might work on a brand new Mac, but people stuck with Lion or, God forbid, the merely 3 years old Snow Leopard (like I am with my 4 year old Mac Mini) will find that Apple is not interested in many developers.

Sorting with Lambdas

Lambdas allow to hand inline expressions to distant algorithms, like the ones used for Qt collections. For example if you want to sort a custom data type in a QList, it was so far necessary to write a custom comparison operator in the class itself or to supply a comparison function (red below):

bool myComparison(const MyStruct&a,const MyStruct&b)
{
  return a.member < b.member;
}

qSort(mylist.begin(), mylist.end(), &myComparison);

Writing a complete function just for a custom sorting is quite cumbersome and can become unnecessarily verbose if you have to implement several options for sorting. An easier option is to use lambdas (green):

qSort(hallo.begin(), hallo.end(), [](const MyStruct&a,const MyStruct&b){return a.member < b.member;} );

You'll find a more complete example below. In this case the lambda expression defines a kind of anonymous inline function that is referenced as a function pointer from here on. The [] symbol starts the lambda, the text between (...) defines the parameters it takes (which must match the content type of the list or other collection that is to be sorted) and the code between {{ and } defines the body of this anonymous function.

One important note: the lambda used for sorting must not reference the environment (i.e. the [&] closure syntax used below cannot be used here), otherwise it will be incompatible with function pointers as they are used by Qt.

Signals and Lambdas

Hint 3: the remainder of this article is obsolete if you are using Qt 5, since it supports natively connecting lambda expressions to signals.

One of the more obvious applications for lambdas would be to connect them to signals. How often did you have to rewrite a function that displays a very simple dialog just because you wanted some trivial interaction? If your programming style is like mine: hundreds of times! Consider this function:

QString filename;
//...

void showDialog()
{
  QDialog d;
  d.setWindowTitle("Example");
  QFormLayout*fl;
  d.setLayout(fl=new QFormLayout);
  QLabel*label;
  fl->addRow("My File:",label=new QLabel(filename));
  QHBoxLayout*hl;
  fl->addRow(hl=new QHBoxLayout);
  hl->addStretch(1);
  QPushButton*p;
  hl->addWidget(p=new QPushButton("Close"));
  d.connect(p,SIGNAL(clicked()),&d,SLOT(accept()));
  d.exec();
}

Now let's suppose you want to make this dialog an editable one. For example if you want to give the user the ability to change the file name. With classic Qt/C++ you would have to refactor the dialog into its own class. With lambda expressions there is an easier way.

While the capability to connect signals to lambdas is planned for Qt 5, it is not available in Qt 4. Fortunately simple lambda expressions can be easily wrapped with a very simple class:

lambda.h:
#include <functional>
#include <QObject>
class Lambda:public QObject
{
  Q_OBJECT
  private:
    std::function<void()>m_ptr;
  public:
    Lambda(std::function<void ()>l,QObject* parent = 0):m_ptr(l){}
  public slots:
    void call(){if(m_ptr) m_ptr();}
};

This defines a very simple wrapper class that on one hand allows to wrap a lambda expression. The expression is limited to one specific type or signature - in this case a lambda that takes no arguments and does not return anything. This should be enough for most cases, since lambda expressions can have access to the objects in surrounding code. If arguments are necessary it is quite likely that the code is complex enough to justify a refactorization or at the very least to write another type of lambda wrapper.

Instead of a simple function pointer we use the special template type std::function. This type represents all kinds of call-able expressions, like lambda expressions, function pointers, and functor objects. std::function is parametrized with the function signature (return type and argument list) of the lambda expression.

The entire class can be implemented inline: the constructor simply instantiates the function object m_ptr and the slot then calls it. The check if(m_ptr) is there to make sure that no accidental call of a null-pointer happens.

The resulting extended dialog now looks like this (the new code is blue, the actual lambda green):
#include "lambda.h"
QString filename;
//....

void showDialog()
{
  QDialog d;
  d.setWindowTitle("Example");
  QFormLayout*fl;
  d.setLayout(fl=new QFormLayout);
  QLabel*label;
  QPushButton*p;
  fl->addRow("My File:",label=new QLabel(filename));
  //begin new functionality
  fl->addRow("Change File:",p=new QPushButton("click to change"));
  Lambda myaction( [&](){label->setText(QFileDialog::getOpenFileName(&d));} );
  d.connect(p,SIGNAL(clicked()),&myaction,SLOT(call()));
  //end new functionality
  QHBoxLayout*hl;
  fl->addRow(hl=new QHBoxLayout);
  hl->addStretch(1);
  hl->addWidget(p=new QPushButton("Ok"));
  d.connect(p,SIGNAL(clicked()),&d,SLOT(accept()));
  //add a cancel button
  hl->addWidget(p=new QPushButton("Cancel"));
  d.connect(p,SIGNAL(clicked()),&d,SLOT(reject()));
  //if the user clicked ok, store the value for next time
  if(d.exec()==QDialog::Accepted)
    filename=label->text();
}

Instead of refactoring the code and writing a completely new class for each dialog we just added three lines of code. The myaction object above wraps the lambda expression and makes it available as a slot. Unfortunately there is not way to wrap the Lambda object into the connect line itself - it would disappear immediately after connect returns.

Instead of [] the lambda begins with [&] above, this is the syntax to give the expression access to all variables that have been declared above it. A lambda that references its environment is sometimes also called a closure. While simple non-referencing lambdas are compatible with function pointers closures require the use of std::function, because they are a special case of functor classes (each lambda/closure is in fact its own anonymous functor class).

There is one danger with closures: if you remember it beyond the visibility of any of the variables that it uses it may cause a crash. This danger is greatly reduced in this case because the Lambda instance has the exact same visibility as any variable it could use. If you instantiate a Lambda instance with new you should take care that it has a parent with the correct visibility, e.g.:

void myFunction()
{
  QDialog d;
  //...
  Lambda *lambda=new Lambda( [&](){qDebug<<"title"<<d.windowTitle();} , &d);
}

A slightly safer way of specifying a closure is to use the copy-syntax. By using [=] instead of [&] the variables that are used by the lambda expression are copied (the lambda knows its context by value instead of by reference), but that is not practical with non-copyable variables (like QObject instances) and it is still dangerous if the variable is a pointer - you may create a dangling pointer by copying one whose content is deleted before the lambda is.

The parentheses () in the examples above are not necessary for lambdas that take no arguments, but they make the code more readable.

Complex Signals

Which brings us to another remaining drawback of this implementation: you have to create a new lambda wrapper class for each function signature that you want to use with a lambda. The implementation above is actually fine for 80% of all cases in which you will want to use a lambda wrapper - most signals either carry no parameters or you will not be interested in them. If you need the paramters or need a return type for the slot/lambda you have to change the implementation of the slot:

class AnotherLambda:public QObject
{
  Q_OBJECT
  private:
    std::function<int(QPoint)>m_ptr;
  public:
    Lambda(std::function<int(QPoint)>l,QObject* parent = 0):m_ptr(l){}
  public slots:
    int call(QPoint p){
      if(m_ptr)return m_ptr(p);
      else return 0;
    }
};

The above lambda wrapper wraps a lambda expression (or function pointer or functor object) that expects a QPoint argument and returns an int.

Unfortunately a more generic solution is not easily accomplished - the Qt meta object compiler cannot create code for templates, much less for C++-11 style variadic templates. So this code will not work:

template<typename Ret,typename...Args>
class<Ret(Args...)>
{
  Q_OBJECT
  private:
    std::function<Ret(Args...)>m_ptr;
  public:
    Lambda(std::function<Ret(Args...)>l,QObject* parent = 0):m_ptr(l){}
  public slots:
    Ret call(Args...args){
      if(m_ptr)return m_ptr(args...);
      else return Ret();
    }
};

The bad news is that moc will refuse to generate code for any template classes. The good news is that moc only generates standard C++ code (albeit it does look a bit ugly at some places). So the code generated by moc can be replicated.

The effects of the Q_OBJECT macro and the code generated by moc have to be simulated through different means. In theory this ought to be very simple - in practice moc generates quite complex code. For the generic template above some slight alterations of what moc normally generated are necessary (generating dynamic instead of static data) and it takes several more variadic templates with multiple specializations to adapt to all eventualities.


Webmaster: webmaster AT silmor DOT de