[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; funcs.push_back([i](){add5(i);});
}
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!