Thursday, 7 May 2009

Using C++0x lambda to replace Boost Bind in C++03 code

[Note: This post looks ugly under Internet Explorer. If you want to tell me how to fix my CSS, that would be much appreciated]
[Note: This post was written using the Intel C++ compiler with -std=c++0x.]

As some of you know, I work on Worklog Assistant which is written using C++/Qt.

One thing that I've made very good use of is a slightly modified version of the code in an earlier post titled "Using Boost Bind and Boost Function with Qt".

As an example, consider the following code from the above app which creates a popup menu to toggle the visibility of table columns. This is done by creating a "toggle" action for each column header that... toggles the visibility. It might help to think how this action would be created in plain Qt (hint: it would be painful.) It would certainly not have the locality it does now.

void
showColumnHeaderContextMenu(ssci::CustomTableView * self,const QPoint & pos)
{
QMenu columns(ssci::CustomTableView::tr("Visible Columns"));
columns.setIcon(QIcon(QString::fromLatin1(":/ui/icons/grid.png")));

QHeaderView * headers(self->horizontalHeader());

std::vector<std::pair<QString,int> > sorted_columns;
getSortedColumns(self->model(),headers,sorted_columns);

for(int ii = 0; ii < headers->count(); ++ii)
{
QAction * action =
new QAction(sorted_columns[ii].first,
&columns);

bool is_hidden = headers->isSectionHidden(sorted_columns[ii].second);

action->setCheckable(true);
action->setChecked(!is_hidden);

using boost::bind;
using boost::function;

function<void()> toggle =
bind(&QHeaderView::setSectionHidden,headers,
sorted_columns[ii].second,!is_hidden);

ssci::connect(action,SIGNAL(triggered()),
toggle);

columns.addAction(action);
}

QMenu menu;
menu.addMenu(&columns);

menu.exec(self->mapToGlobal(pos));
}


Of particular interest is the code in the for loop which sets up the actions:
    function<void()> toggle =
bind(&QHeaderView::setSectionHidden,headers,
sorted_columns[ii].second,!is_hidden);

ssci::connect(action,SIGNAL(triggered()),
toggle);



This creates a function object on the fly which hides the selected column using code from the above linked post. To do this in plain Qt is a gigantic pain. LibQxt has a solution as well but mine is much better!

Anyway, the point is that you might find a lot of code using bind and function in an app like this. Binding member function pointers, member data pointers and function pointers is fairly normal and is really the only way to maintain sanity and reduce boiler plate. Nested binds are also used where appropriate.

Boost Bind is a way to create closures, or at least as close as you can get in C++ to true closures. Therefore it is natural to try and replace some uses of Boost Bind with C++0x lambda. There is good coverage of how C++0x lambda works here so I won't repeat the same information. However, I will cover the cases of bind I could convert and (horrifically) the cases I couldn't!

Using automatic variables



Consider the following code:

const int LICENSING_TAB_INDEX = 1;

ssci::connect(lblImportNewLicense,SIGNAL(linkActivated(QString const &)),
boost::function<void()>(
boost::bind(&QTabWidget::setCurrentIndex,
tabWidget,
LICENSING_TAB_INDEX)
)
);



The above function call sets things up so that when a link is clicked in the interface, the LICENSING_TAB_INDEX tab is selected in the configuration.

One thing Boost Bind does is that it stores all bound arguments by value. That means &QTabWidget::setCurrentIndex, tabWidget and LICENSING_TAB_INDEX are all stored by value. C++0x lambda calls this capturing. This can be done using implicit capture (by parsing the lambda body) or by you. By default, C++0x lambda captures variables by reference. It is potentially buggy to capture function-local variables by reference automatically. Therefore, the above code translated to C++0x lambda is:
[=]()
{ tabWidget->setCurrentIndex(LICENSING_TAB_INDEX); }

The first element of a lambda-introducer (the []) can be a capture default which can be one of = or &. The capture default tells the compiler how to capture those variables that are implicitly captured. The assignment is supposed to make you think of copy assignment and the & is supposed to make you think of reference to. So in the above case, to get the exact same behaviour as the bind, we need to intentionally copy all variables by value.

Great! Not so bad :-)

A simpler example... Or is it?



Consider the following code:

ssci::connect(clearSelectedRole,SIGNAL(clicked()),
boost::function<void()>(
boost::bind(
&QComboBox::setCurrentIndex,
projectRoles,
-1)
)
);


This code sets things up so that when the clearSelectedRole button is clicked, the corresponding QComboBox has an invalid index.

The C++0x version of this is really straightforward:

ssci::connect(clearSelectedRole,SIGNAL(clicked()),
boost::function<void()>(
[&](){projectRoles->setCurrentIndex(-1);}
)
);

In this case, the pointer object (not the value) projectRoles is captured by reference. This is not an issue because the pointer object is guaranteed to outlive the closure. Now you know why closures and garbage collection go together :-)

Verbosity or Why I wish C++0x used polymorphic lambdas



Consider the following code:

static
void
updateThreadSetup(QObject * parent,ssci::MainWindow * self);

m_checkForUpdatesThread(boost::bind(updateThreadSetup,_1,&parent))


The bound function in this case is an object that calls the function updateThreadSetup with a to-be-determined value for the first parameter and a fixed value for the second parameter.

The C++0x version of this is horrible:
m_checkForUpdatesThread([&parent](QObject * obj){updateThreadSetup(obj,&parent);});


First, since parent is a local variable (was passed into the function), you can't implicitly capture it. So you have to explicitly capture it. In this case, we want to capture it by reference, hence [&parent] in the lambda-introducer. Then, since this thread setup function is passed in a QObject* we have to tell the compiler to accept this argument. Apparently it isn't smart enough. Ask someone why it is this way, you'll hear some hand waving about the callable concept. Whatever.

That is some ugly code.

Anyway...

What you cannot convert without making your code super ugly



Consider the following simple code:
vector<int> d = {1,2,3,4,5};
vector<function<int()>> funcs;

for(vector<int>::iterator it = d.begin(), end = d.end();
it != end;
++it)
{
funcs.push_back(boost::bind(add5,*it));
}


This creates a vector of function objects that presumably add 5 to their bound argument and return the result.

Here is the equivalent in C++0x lambda:

vector<int> d = {1,2,3,4,5};
vector<function<int()>> funcs;

for(vector<int>::iterator it = d.begin(), end = d.end();
it != end;
++it)
{
int &i = *it; // WTF?
funcs.push_back([i](){add5(i);});//THIS IS REDUNDANT. REDUNDANT.
}


Naively, one might have done:

funcs.push_back([it](){add5(*it);});

in the loop. But this is a bug waiting to happen. Add the following line right after the for loop:
d.push_back(0)

Now all those iterators that are captured by value can be invalidated! YAY! The only way to avoid this issue is to manually extract the value referenced by the iterator and pass that into the closure which is what I did in my translation.

Conclusion



This exercise showed me that C++0x lambda is of some interest to me. I'd really like to get rid of the verbosity (I know, too late!) A couple of things would make this the perfect lambda for me:

  • Polymorphic lambdas (or at least let me specify auto for the arguments)

  • An easy way to capture computed values (the *it bug above)



I don't know how these problems would be solved, or whether they could be but until they are, I think there is still a future for function binding ala Boost Bind which is fine by me!

Monday, 4 May 2009

BoostCon 2009

BoostCon starts tomorrow.

I went to the first and second Boost conferences. The first as an attendee, the second as a speaker. I was blown away by the quality of the talks, particularly the author's corner series of talks. I think that if the organizers are smart about it, this can be one of the best conferences. For me, it is already on par with conferences like SD West for quality and relevance.

I am extremely disappointed that I could not make it this year due to my schedule being way too busy. I hope that some of you reading this have been able to schedule better than me and go to BoostCon.

If you are attending, please ask at least one very difficult on-topic question in each of your talks for me.

Also, ask Joel de Guzman about Boost.GUI. Apparently he's got something cooking (you didn't hear it from me!)