Reputation: 2959
Let's say we need to iterate over a container. The traditional for loop would look like this:
for (auto it = container.begin(), end = container.end();
it != end;
++it)
{
doStuff(*it);
}
while the range-based for would look like this:
for (auto& element : container)
{
doStuff(element);
}
Now, at some point of the development we realize that for some reason or other, we need to increment something else over those loop iterations.
What needs to be incremented may be various things. For example, if we have relevant data stored in other containers of the same size, we may need to increment iterators to those containers too as we go through the iterations (though I hope future version of the standard library will allow us to do that more expressively, through structured bindings and a standard version of boost::range::combine or something).
In the following, to keep things simple, we'll assume that we want to attribute an ID to each element, so what needs to be incremented is simply a counter.
The traditional loop would now look like this:
unsigned int elementID = 0u;
for (auto it = container.begin(), end = container.end();
it != end;
++it, ++elementID)
{
doStuff(*it, elementID);
}
Barely anything has to be changed, and adding the ++elementID
after the ++it
guarantees that the counter will be incremented no matter what after each iteration. Even if another programmer were to modify the body of the loop and, say, go early to the next iteration under certain conditions through continue
, there would be no risk of them forgetting to increment the counter or anything like that.
Now, with the range-based for, as far as I can tell, the only way to do the incrementation would be to do something like this:
unsigned int elementID = 0u;
for (auto& element : container)
{
doStuff(element, elementID);
++elementID;
}
That is to say, to put the incrementation within the body of the loop.
This is less expressive with regards to elementID
(i.e. if the body of the code is long, someone reading the code will not see at a glance that we're iterating over elementID
too), and it doesn't give the guarantee that I've mentioned above, so it is also prone to error.
Is there really no other way to implement this with a range-based for? Or is there a way to write something along the lines of for(auto& element : container; ++elementID){...}
that I'm simply not aware of?
Edit after people answered
Nevin suggested boost's BOOST_SCOPE_EXIT_ALL, which is the closest to what I had in mind as far as non-native solutions go.
I'm not sure about the actual implementation, but I guess this relies on lambdas and destructors. I wrote this to test it out:
template <typename T>
class ScopeExitManager
{
public:
ScopeExitManager(T const& functionToRunOnExit) : _functionToRunOnExit(functionToRunOnExit)
{
}
~ScopeExitManager()
{
_functionToRunOnExit();
}
private:
T _functionToRunOnExit;
};
template <typename T>
ScopeExitManager<T> runOnScopeExit(T const& functionToRunOnExit)
{
return {functionToRunOnExit};
}
Which then allowed me to write something along the lines of:
unsigned int elementID = 0u;
for (auto& element : container)
{
// Always at the beginning of the loop
auto scopeExitManager = runOnScopeExit([&elementID](){++elementID;});
// Actual body of the loop
doStuff(element, elementID);
}
which is expressive and guarantees that elementID
will be incremented. This is great!
Upvotes: 5
Views: 2539
Reputation: 218278
With range-v3 (which should be part of C++20), you might do something like:
for (const auto& p : ranges::view::zip(container, ranges::view::ints)) {
doStuff(p.first /* element*/ , p.second /*ID*/);
}
Upvotes: 1
Reputation: 4863
Another way to accomplish this is to use something like Boost.ScopeExit, as in:
unsigned int elementID = 0u;
for (auto& element : container)
{
BOOST_SCOPE_EXIT_ALL(&) { ++elementID; };
doStuff(element, elementID);
}
Upvotes: 5
Reputation: 19617
No, the syntax does not allow any more than an init-statement (since C++20).
Technically, you can embed additional statements to be performed at the time of iterator comparison and/or incrementation inside range_expression. This can be done by creating a generic wrapper class for containers, implementing its own iterators in terms of the ones of the provided container + additional logic:
auto additional_condition = [](auto const& element) { return ...; };
auto increment_statement = []() { ... };
for(auto& element :
custom_iterable(container, additional_condition, increment_statement))
{
...
}
But it largely defeats the purpose of the range-based for (simpler syntax). The classic for loop is not that bad.
Let's not forget macros - they probably have the power to:
but let's not use them in this way, either. That would be just rude.
Upvotes: 4