Click here to Skip to main content
15,921,250 members
Articles / Programming Languages / C++
Article

Expression-based callbacks

Rate me:
Please Sign up or sign in to vote.
4.60/5 (7 votes)
12 Mar 20062 min read 51K   17   18
An easy way to provide expression-based callbacks in STL containers.

Introduction

In most cases where collections are used, the need to iterate over a collection, do a computation, and collect the results in a variable, arises often. This article shows an easy way to do this in C++, different from the most common approach, using containers together with expression templates. The article will use STL, but the technique can be applied to any container.

The common approach

The most usual approach to iteration over a collection is to use a for-loop. Writing for loops becomes tedious after a while, therefore many languages offer a 'for-each' statement that allows iteration over a collection. While C++ does not offer such a statement, it is very easy to implement it in various ways. The most common approach is to make a for-loop in a function that accepts a callback, like this:

template <class C> void forEach(C &c, 
            void (*callback)(C::value_type)) {
    for(C::iterator it = c.begin(); 
                    it != c.end(); ++it) {
        callback(*it);
    }
}

But this approach has some disadvantages; it interrupts the thought process of the programmer by making him/her think about where to place the callback, and also how to access the local data from inside the callback.

Expression templates

Expression templates come as a rescue: by using operator overloading, classes that represent an expression bound to local arguments can be used as a callback to a for-each loop. Before explaining how to code such classes, let's see an example of usage:

#include <list>
#include <iostream>
using namespace std;

int main()
{
    list<int> l;
    
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    
    int i = 0;
    Expr< int > x;
    forEach(l, i, x += i);
    cout << x;
    getchar();
    return 0;
}

As you can see, it is much easier to sum a list of numbers using the above code!

How it works

Here is the important line of code; let's focus on it:

forEach(l, i, x += i);

C++ does not have lambda functions or closures, but it has operator overloading. So in the above code, x is an object that when operator += is applied to it, it does not actually add anything, but it creates a function object that encapsulates the variables x and i, and when invoked, it will perform the action.

The class Expr that x is an instance of is:

template <class T> class Expr {
public:
    Expr() : m_v(T()) {
    }

    ExprAddAssign<T> operator += (T &i) {
        return ExprAddAssign<T>(m_v, i);
    }
    
    operator T () const {
        return m_v;
    }
    
private:
    T m_v;    
};

We can see that it encapsulates a value of type T. We also see that operator += returns an instance of class ExprAddAssign, which is this class:

template <class T> class ExprAddAssign {
public:
    ExprAddAssign(T &lv, const T &rv) : 
                  m_lv(lv), m_rv(rv) {
    }
    
    void operator ()() const {
        m_lv += m_rv;
    }

private:
    T &m_lv;
    const T &m_rv;
};

The above class contains two bindings: one for the rvalue and one for the lvalue. The lvalue is assigned the value of rvalue. The code for the function 'forEach' becomes:

template <class C, class T, class F> 
         void forEach(const C &c, T &v, F &obj) {
    for(C::const_iterator it = c.begin(); 
                          it != c.end(); ++it) {
        v = *it;
        obj();
    }
}

Conclusion

A wide variety of expression templates can be programmed. Even constructs like if-then-else can be coded using a Smalltalk like syntax. Example:

forEach(l, (i > 2).ifTrue(x += i));

I actually accidentally came up with this solution, searching for a way to make lambda functions in C++. I realized that one does not need lambda functions, as long as lambda parameters can be declared as local variables.

I am surprised that the STL library does not contain such a set of classes. It would offer great power to C++...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
Greece Greece
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralBoost Pin
Stephen Hewitt12-Mar-06 20:41
Stephen Hewitt12-Mar-06 20:41 
GeneralRe: Boost Pin
Stuart Dootson12-Mar-06 21:03
professionalStuart Dootson12-Mar-06 21:03 
GeneralRe: Boost Pin
Stephen Hewitt12-Mar-06 21:44
Stephen Hewitt12-Mar-06 21:44 
GeneralRe: Boost Pin
Achilleas Margaritis13-Mar-06 0:07
Achilleas Margaritis13-Mar-06 0:07 
GeneralRe: Boost Pin
Stephen Hewitt13-Mar-06 0:29
Stephen Hewitt13-Mar-06 0:29 
GeneralRe: Boost Pin
Achilleas Margaritis13-Mar-06 4:25
Achilleas Margaritis13-Mar-06 4:25 
GeneralRe: Boost Pin
Stephen Hewitt13-Mar-06 11:31
Stephen Hewitt13-Mar-06 11:31 
GeneralRe: Boost Pin
Achilleas Margaritis13-Mar-06 11:51
Achilleas Margaritis13-Mar-06 11:51 
GeneralRe: Boost Pin
Stephen Hewitt13-Mar-06 11:55
Stephen Hewitt13-Mar-06 11:55 
GeneralRe: Boost Pin
Achilleas Margaritis14-Mar-06 0:09
Achilleas Margaritis14-Mar-06 0:09 
GeneralRe: Boost Pin
Ivan Kolev14-Mar-06 20:53
Ivan Kolev14-Mar-06 20:53 
There are many people who refuse to use Boost for some reasons, especially the heavier libraries like Lambda, mpl, and others. They prefer simpler approaches like the one described in this article. Sometimes they even rip some simple and elegant solution from Boost to avoid the tons of dependencies it would incur if used directly.

Specifically about Boost Lambda, I'd like to quote a recent post on comp.lang.c++.moderated:
"I wouldn't use [Boost Lambda and Phoenix] in a production code. For example, I really do not want to see Boost.Lambda expression in a call stack when I am looking at a core dump of a production problem. Also, the syntax of any nontrivial Boost.Lambda expression is just unreadable (as it introduces a new syntax for already existing C++ constructs). Not to mention that the compiled code is dog slow if inlining is disabled."

So I think articles like this one "reinventing" something that's been done in Boost or anywhere else in a simpler way are always welcome, as long as they mention the existing alternative solutions and compare the (dis)advantages of all approaches.
GeneralRe: Boost Pin
Stephen Hewitt14-Mar-06 22:26
Stephen Hewitt14-Mar-06 22:26 
GeneralRe: Boost Pin
Jerry Jeremiah20-Jul-06 12:37
Jerry Jeremiah20-Jul-06 12:37 
GeneralRe: Boost Pin
Stephen Hewitt20-Jul-06 14:13
Stephen Hewitt20-Jul-06 14:13 
GeneralRe: Boost Pin
Jerry Jeremiah20-Jul-06 19:22
Jerry Jeremiah20-Jul-06 19:22 
GeneralRe: Boost Pin
Stephen Hewitt20-Jul-06 19:52
Stephen Hewitt20-Jul-06 19:52 
GeneralRe: Boost Pin
Jerry Jeremiah23-Aug-09 12:57
Jerry Jeremiah23-Aug-09 12:57 
GeneralDLinq Pin
Keith Farmer12-Mar-06 15:18
Keith Farmer12-Mar-06 15:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.