Game Development by Sean

Trivial C++ For-Each Loop for MSVC 2010

Table of Contents

The new C++11 standard includes a nice new feature called range-based for. The syntax allows for simple and quick iteration over ranges, such as containers. Usage looks like so:

for (int& x : my_container) { do_something_with(i); }

Pretty handy. Unfortunately, this new feature is not supported in MSVC 2010. In fact, very few C++11 features are supported in MSVC, while GCC supports a large majority of them. One of the popular solutions to many C++ problems like this one is to make use of Boost, which does include a ForEach library that can roughly simulate the new C++11 syntax. However, this facility is quite heavy-weight and like most Boost libraries it includes some very complicated templates which can have a very detrimental effect on compile times.

Users of MSVC (and any other recent C++ compiler) fortunately do have access to some other facilities of C++11 that make it significantly easier to implement a this loop construct with a minimal of fuss. While Boost attempts to service every major C++ compiler still in reasonably popular use, those of us who are explicitly targeting MSVC 2010 can make use of one of these other C++11 features to make a very small and efficient FOR_EACH macro. In fact, the implementation I’m presenting does not add any templates or other types at all; it’s just a simple four-line macro declaration that is free of side-effects and works with no surprising behavior in any enclosing context, and has absolutely no run-time overhead compared to a traditional iterator-based for-loop or any comparable hand-coded loop!

I must note that in my container and algorithm codebase I make use of ranges rather than STL-style iterators. For more information on that topic (which I plan on writing about later), check out the Andrei Alexandrescu’s paper Iterators Must Go. I’m going to assume for the rest of this article that the reader is familiar with the concept of ranges vs iterators and is using ranges in their own containers and algorithms, as my foreach implementation depends on them. Some sample code at the end of this article uses STL idioms for users not using a custom container and algorithm library.

I’m just going to post the code I have for FOR_EACH and then go through explaining how it works.

#define FOR_EACH(VAR, RANGE) \ for (auto _foreach_range = (RANGE); !_foreach_range.empty(); _foreach_range.pop_front()) \ if (bool _foreach_inner = false) {} else \ for (VAR = _foreach_range.front(); !_foreach_inner; _foreach_inner = true)

The one feature of C++11 that this code depends on is the new behavior for the auto keyword. This allows us to create a copy of the given range so that the input is only evaluated once, which is important to ensure that the macro has no unintended side-effects.

The second line has that odd-looking IF statement in the code. It exists to create a variable that we need for the second FOR statement. However, we have to be tricky and put that “{} else” bit in the code to ensure that the IF statement we made doesn’t bind to any other ELSE clauses. If we didn’t do that, the following code would not work correctly:

if (do_loop) FOR_EACH(int i, my_int_vector) printf("%i\n", i); else printf("no data\n");

The next two lines are where things get tricky. We want our macro to allow the user declare a variable to start the values being enumerated, including the variable type. That is going to require creating a new variable in a new scope if we want to support references, which we do. We can’t do that with an IF statement on its own because that requires the variable to be convertible to false, which is of course not true for arbitrary types (or even most types, whose bool conversion tends to have some meaning attached to it rather than always being false). The only construct we have for creating a new variable for a following statement is to use a FOR statement. Howevever, that is a looping statement, and we only want to execute the following statement once. We’re going to have to construct a FOR statement that will run once and only once every time. To do that, we need a second variable to control the loop.

Enter the IF statement in the FOR_EACH macro. The _foreach_loop is a boolean value initialized to false. We initialize to false to make sure the ELSE clause that actually holds our FOR statement is always run. We then use that boolean in the following FOR statement to ensure it runs only once. The boolean is toggled to true after the loop is run once and then the loop terminates due to the condition !_foreach_loop.

The second FOR statement creates our local user-specified variable initialized to the front value from the range (which for STL adaptors is the same as initializing it to the iterator’s dereferenced value). The macro is complete; the body of the FOR statement is the statement the user entered after the FOR_EACH macro. We now have a simple means of iterating over any range, like so:

FOR_EACH(auto i, container.all()) { do_something(i); }

That’s all there is to it!

For those using the STL, it should be trivial to convert the code to operate on a pair of iterators instead of a range object. Modify the above code to use a std::pair<> of iterators and update the empty(), pop_front(), and front() calls with equivalent code: first != second, ++first, and *first, respectively. The code should look something like the following snippet. As I haven’t tested this version, note that if it breaks the reader gets to keep the pieces.

#define FOR_EACH(VAR, BEGIN, END) \ for (auto _foreach_range = std::make_pair((BEGIN), (END)); _foreach_range.first != _foreach_range.second; ++_foreach_range.first) \ if (bool _foreach_inner = false) {} else \ for (VAR = *_foreach_range.first; !_foreach_inner; _foreach_inner = true)

FOR_EACH(int i, int_vector.begin(), int_vector.end()) { printf(“%i\n”, i); }</code>

p.s. Forgive the use of printf rather than C++ iostreams; the blog is not liking the less-than signs used for the stream-out operators, and I’m not much in the mood to wrestle with WordPress right now.</div>