Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range-based for and other increments

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!

like image 649
Eternal Avatar asked Jun 14 '19 19:06

Eternal


People also ask

What are range-based for loops?

Range-based for loop (since C++11) Executes a for loop over a range. Used as a more readable equivalent to the traditional for loop operating over a range of values, such as all elements in a container.

Are range-based for loops faster?

Advantages of the range-based for loop We can easily modify the size and elements of the container. It does not create any copy of the elements. It is much faster than the traditional for loop.

How do you define a range in CPP?

Use the range-based for statement to construct loops that must execute through a range, which is defined as anything that you can iterate through—for example, std::vector , or any other C++ Standard Library sequence whose range is defined by a begin() and end() .


2 Answers

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);
}
like image 174
Nevin Avatar answered Oct 12 '22 06:10

Nevin


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:

  1. re-implement range-based for
  2. add more comma-separated arguments

but let's not use them in this way, either. That would be just rude.

like image 30
LogicStuff Avatar answered Oct 12 '22 06:10

LogicStuff