Click here to Skip to main content
Click here to Skip to main content

Expression-based callbacks

By , 12 Mar 2006
 

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

About the Author

Achilleas Margaritis
Software Developer (Senior)
Greece Greece
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralBoostmemberStephen Hewitt12 Mar '06 - 20:41 
You should have a look at the Boost.Lambda[^] library.
 
Steve
GeneralRe: BoostmemberStuart Dootson12 Mar '06 - 21:03 
It's amazing how often I see an intriguing article title in CP, have a quick look and think 'I bet there's a comment suggesting looking at Boost' Smile | :)
GeneralRe: BoostmemberStephen Hewitt12 Mar '06 - 21:44 
Yeah. But Boost contains a lot or really cool cutting edge stuff. I find it a little amazing how often you read a Code Project article and find someone has made a homespun version of something which Boost provides a free industrial strength version of. This is not meant as a criticism of this article – I only had a superficial look at it – But based on that (by my own admission superficial) examination it looks a lot like Boost.Lambda.
 
Steve
GeneralRe: BoostmemberAchilleas Margaritis13 Mar '06 - 0:07 
Actually my article was about a style of lambda different than boost. I do not particularly like the boost approach of 'placeholder variables', 'bind' etc. The reasons are:
 
1) the code looks nicer without all the ugly '_1', '_2' etc which, after a while, it is not easy to read.
 
2) the boost approach is difficult for newbies (newbies to C++, newbies to boost). I may use '_1' and 'X' as placeholders, but the next person to be assigned to the project may be confused by it.
 
3) using placeholder variables may have undesired side effects. For example, the code 'cout << _1 << 1' and code 'cout << 1 << _1' are executed very differently, although they look particularly similar.
 

GeneralRe: BoostmemberStephen Hewitt13 Mar '06 - 0:29 
If the names of the placeholders offend you when using Boost.Lambda you can add your own.
 
How would you do something like this using your system?
typedef vector<int> Ints;
Ints i1, i2, o;
// Code here which fills "i1" and "i2" with numbers.
transform(i1.begin(), i1.end(), i2.begin(), back_inserter(o), _1+_2);
 
This code fills the vector "o" by adding corresponding values from "i1" and "i2". The actual Lambda expression "_1+_2" is concise and the placeholders add to the clarity rather then subtract from it.

 
Steve
GeneralRe: BoostmemberAchilleas Margaritis13 Mar '06 - 4:25 
I would do it like this:
 
typedef vector<int> IntVector;
IntVector i1, i2, o;
 
//bla bla code which fills 'i1' and 'i2' with numbers

Var<int> i, j;
forEach(i, i1, j, i2, pushBack(o, i + j));
 

GeneralRe: BoostmemberStephen Hewitt13 Mar '06 - 11:31 
You've reinvented multiple wheels here:
- You use a home spun version of for_each which is a standard STL algorithm. The version of forEach you use hasn't been defined anywhere in but it looks like, if written, would be like the STL algorithm transform.
- You use a home spun version of back_inserter which is a standard STL iterator.
 
You also introduce two local variables. Does introducing all these new constructs actually make like easier for "newbie’s"? It will make it harder on the "oldbie's". Given that many Boost libraries will make it into the next version of STL you also run the very real risk of reinventing STL instead of augmenting it.

 
Steve
GeneralRe: BoostmemberAchilleas Margaritis13 Mar '06 - 11:51 
Hey, relax Smile | :) , I am just communicating some ideas. Personally I do not take STL/boost as the "one and true way" of doing things. Please allow some disagreement!

GeneralRe: BoostmemberStephen Hewitt13 Mar '06 - 11:55 
I am relaxed - Maybe you should (relax), it sounds like you're interpreting my comments as an attack. Feedback on articles and the expression of alternate approaches is what the message board at the end of each article is for. I would argue that your article wasn't complete without mentioning Boost.Lamda but now I've rectified that for CP readers.
 
Steve
GeneralRe: BoostmemberAchilleas Margaritis14 Mar '06 - 0:09 
There is also FC++, for those readers that want to investigate this further.
GeneralRe: Boostmemberikolev14 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: BoostmemberStephen Hewitt14 Mar '06 - 22:26 
On Boost.Lamda:
 I would only use Boost.Lambda for extremely simple functions. If you have to write a complex Lambda function it would almost certainly be better to write a function or function object instead. That said one of the problems with STL is that code tends to become littered with 100s of trivial function objects - This is the problem I think Boost.Lamda is ideal for solving. Also bear in mind that to do Lambda functions properly language extension would be required (which has actually been suggested).
 
I've got nothing against this article as such - But I did feel the need to mention the mature alternatives I was aware of (Boost.Lamba).

 
Steve
GeneralRe: BoostmemberJerry Jeremiah20 Jul '06 - 12:37 
I just want to say:
 
I think boost has lots of great stuff. I really like the syntax of boost::spirit and, although I hadn't previously looked at boost::lambda it looks pretty cool as well.
 
Traditionally I have never used boost just because it is so heavy... one huge library (STL) is enough.
 
For that reason, if I were going to use something like this, I would probably roll my own. So having seen this article really helps me clarify in my own mind how I could do it and what it could look like.
 
Personally, if I were going to develop something like this myself, I would try to use the standard for_each algorithm but that is a personal design decision.
 
I found that the solution, just as it was, really got me thinking and that the is main reason I read these articles (I have never actually used any of the code I found on codeproject...).
 
I thought it was a good article. It probably should have mentioned boost since boost seems to have something for everything anyone ever needs (true in this case as well!) but that doesn't detract from the usefulness of this article (not for me anyway).
 
Even this thread of comments was worth reading.
Thanks to both of you.
 
Jerry
GeneralRe: BoostmemberStephen Hewitt20 Jul '06 - 14:13 
Jerry Jeremiah wrote:
Traditionally I have never used boost just because it is so heavy

 Boost isn't "heavy". Obviously it depends on which libraries you use for the most part it's small header file only classes. STL isn't huge either. I'm not sure what gives you this impression.
 
Steve
GeneralRe: BoostmemberJerry Jeremiah20 Jul '06 - 19:22 
I write embedded software. I usually have a "reasonable" amount of memory, but I have had as little as 2K of memory for my software. Usually libraries that are "full featured" are worse memory wise than stripped down classes that implement EXACTLY and ONLY the functionality needed. So, really it depends on how you define "heavy".
 
STL isn't heavy for desktop and server apps. Ever tried to write software with it on a machine with less than 1MB of memory?
 
As I understand it individual boost features have dependencies on other pieces so using just one feature and not including any other part of the boost library is very hard. Now, like I said before, I have avoided using it so I could be wrong about this...
 
If I could fit a parser that used boost::spirit into less than 10K of memory I would probably use it. Does adding a parser based on boost::spirit add more than a 10K footprint to an executable?
 
Jerry
GeneralRe: BoostmemberStephen Hewitt20 Jul '06 - 19:52 
Spirit is one of the libraries I can't use: I use MSVC 6 and it doesn't work on it. It's interesting to look at the Boost page and see which libraries work on which compilers - MSVC 6 is easily the worst modern compiler still in common use.
 
I tried out "BOOST_FOREACH" the other day and compared the generated machine code (in a release build) to a hard written loop. To my surprise it was actually smaller. Although if you look superficially at the code behind "BOOST_FOREACH" you would not predict this much of it is template meta-functions which are evaluated at compile time and don't contribute to the size of the generated code.
 
You may be similarly surprised if you give some of the libraries a second look.

 
Steve
GeneralRe: BoostmemberJerry Jeremiah23 Aug '09 - 12:57 
I stumbled across this article again just now and read it. Then thought I should post a comment. Then I read the comments and realized I already did!
 
I would like to say, I like this article because I want to know how these things work on the inside and looking at Boost source code makes my eyes water. That is the value of CP.
 
Having another year under my belt (but still using MSVC 6 for my Windows development) I would like to say:
- I still can't figure out how to use one specific bit of Boost on its own because every bit of Boost has dependancies on other bits of Boost.
- I am using the wrong compiler - I can't even write my own stuff because the compiler is so sub-standard. Apparently newer versions are better.
 
If I get a newer compiler Boost or POCO are definately things I will use. But I still really like articles like this because I NEED to know how it works on the inside. You can't do embedded development without understanding exactly what the code does. Unfortunately, this has made me expect that from my Windows development as well.
 
Perhaps you wouldn't use this code in preference to the Boost stuff, but using it as a learning tool is the reason I joined CP. The author should have mentioned that Boost had equivalent functionaliy and then explained why he preferred his version. That would have been most helpful but doesn't substantially detract from the utility I got from reading the article.
 
Jerry
GeneralDLinqmemberKeith Farmer12 Mar '06 - 15:18 
There are some fellow C++ users (C++/CLI, actually) who would be delighted to see how this could be fully fleshed out to support the expression trees in DLinq.
 
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=294311&SiteID=1[^]
 
-----
Keith J. Farmer [MSFT:VC#/DLinq]

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 12 Mar 2006
Article Copyright 2006 by Achilleas Margaritis
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid