If you’re using container classes in your C++ code (and you probably should be, even if it’s just std::array) then one of the things you’re going to want to do (a lot) is iterate through the container accessing each member in turn.
Without resorting to STL algorithms we could use a for-loop to iterate through the container.
If the above is baffling to you there are plenty of useful little tutorials on the STL on the Internet (For example, this one)
We could simplify the iterator declaration in C++11 using auto:
(See the article on auto type-deduction for details of how it works)
However, there’s a nicer syntactic sugar to improve our code: the range-for loop:
The semantics of the range-for are: For every element in the container, v, create a reference to each element in turn, item.
The above code is semantically equivalent to the following:
Look familiar?
Not only does this save you some typing but, because it’s the compiler that’s generating the code it has a lot more potential for optimisation (for example, the compiler knows that the end() iterator is not invalidated in the body of the range-for statement, therefore it can be read once before the loop; or the compiler may choose to unroll the loop; etc.)
In case you were wondering, std::begin() and std::end() are free functions that return an iterator to the first element in the supplied container and an iterator to one-past-the-end, respectively. For most STL containers they simply call cont.begin() and cont.end(); but the functions are overloaded to handle built-in arrays and other container-like objects (see below)
Range-for loops are not limited to STL containers. They can also work with built-in arrays:
And also std::initializer_lists
More information
Can’t wait? Download the full set of articles as a PDF, here.
To learn more about Feabhas’ Modern C++ training courses, click here.
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.
Hi Glennan,
Very nice post but I wondered why we would always have to pre-increment the iterator in the non inclusive loop ?
I just tested myself and the behavior between post-increment and pre-increment seems the same.
And if it is for an optimization purpose, do we really have to do this ? I mean, the compiler will fix it easily I suppose ?
Hi, that's actually not quite correct - the ranged-for loop is semantically equivalent to:
for (auto it = std::begin(r), end = std::end(r); it != end; ++it) {
auto &item(*it);
...
}
So the end iterator is evaluated only before the loop.
See: https://en.cppreference.com/w/cpp/language/range-for
One issue - in your 'unwrapped' version of the range-for loop, std::end is called on every iteration which is not the case.
Both begin and end are called only on initialization.
Lets hope this gets rendered properly:
auto && __range = range_expression;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin)
{
range_declaration = *__begin;
loop_statement
}
Matt,
Good catch. I've updated the original post to reflect this.
Thanks!
Ivan,
I've updated the post to reflect this.
Thanks!