2/23/2011

02-23-11 - Some little coder things - Loop

We talked a while ago about how annoying and error-prone for loops are in C. Well at first I was hesitant, but lately I have started using "for LOOP" in earnest and I can now say that I like it very much.

#define LOOP(var,count) (int var=0;(var) < (count);var++)
#define LOOPBACK(var,count) (int var=(count)-1;(var)>=0;var--)
#define LOOPVEC(var,vec)    (int var=0, loopvec_size = (int)vec.size();(var) < (loopvec_size);var++)

so for example, to iterate pixels on an image I now do :

for LOOP(y,height)
{
    for LOOP(x,width)
    {
        // do stuff
    }
}

the way I can tell that this is good is because I find myself being annoyed that I don't have it in my RAD code.

There are tons of advantages to this that I didn't anticipate. The obvious advantages were : less bugs due to mistakes in backwards iteration with unsigned types, reducing typing (hence less typo bugs), making it visually more clear what's happening (you don't have to parse the for(;;) line to make sure it really is a simple counting iteration with nothing funny snuck in.

The surprising advantages were : much easier to change LOOP to LOOPBACK and vice versa, much easier to use a descriptive variable name for the iterator so I'm no longer tempted to make everything for(i).

One thing I'm not sure about is whether I like LOOPVEC pre-loading the vector size. That could cause unexpected behavior is the vector size changes in the iteration.

ADDENDUM :

Drew rightly points out that LOOPVEC should be :


#define LOOPVEC(var,vec)    (int var=0, var##size = (int)vec.size();(var) < (var##size);var++)

to avoid variable name collisions when you nest them. But I think it should probably just be

#define LOOPVEC(var,vec)    (int var=0; (var) < (int)vec.size(); var++)

Though that generates much slower code, when you really care about the speed of your iteration you can pull the size of the vec out yourself and may do other types of iterations anyway.

8 comments:

dfan said...

Mine is

#define FOR_EACH( it, coll ) for (coll##_TYPE::iterator it = coll.begin(); it != coll.end(); ++it)

and then I can just type e.g.

FOR_EACH( it, mNotes ) { (*it)->Play(); }

I then do have to #define a foo_TYPE for each collection foo, but since I am generally only doing this for member variables, it's not so bad.

This is the only way I can bear to deal with STL iterators rather than indices.

cbloom said...

"#define FOR_EACH( it, coll ) for (coll##_TYPE::iterator it = coll.begin(); it != coll.end(); ++it)"

Hmm.. the need for _TYPE types without typeof or auto types.

It will be much nicer in C++0x

cbloom said...

I should also note that the disadvantages of this sort of meta-language macroing are many. It makes code harder to share. It makes code harder for people who aren't in your system to read. It makes debugging harder if there's ever a problem in the macro. It can create bugs due to the macro not doing exactly what's expected. etc.

Tom Forsyth said...

Interesting that you put the "for" outside the macro. Does this help something non-obvious, or is it just a stylistic thing?

cbloom said...

It's just stylistic. In the previous discussion someone suggested it. I do think there is merit in leaving the flow control indicators outside of the macros.

Jaba Adams said...

For some more inspiration, or perhaps brain-meltage, check out Common Lisp's LOOP macro / mini-language:

http://www.gigamonkeys.com/book/loop-for-black-belts.html

jeskola said...

#undef

cbloom said...

LOL, well played.

old rants