Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range-based for loop with special case for the first item

I find myself often with code that looks like this:

bool isFirst = true;
for(const auto &item: items)
{
    if(!isFirst) 
    { 
       // Do something
    }
    // Normal processing
    isFirst = false;
}

It seems that there ought to be a better way to express this, since it's a common pattern in functions that act like a "join".

like image 457
bpeikes Avatar asked Feb 22 '19 17:02

bpeikes


People also ask

What is a range-based for loop?

Remarks. 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() .

How does range-based for loop work in C++?

Range-based for loop in C++ Range-based for loop in C++ is added since C++ 11. It 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.

What is the range of loop?

Example 1: Ranged for Loop Using Array Note: The ranged for loop automatically iterates the array from its beginning to its end. We do not need to specify the number of iterations in the loop.

What is the difference between for range loop and?

The "for" loop For loops can iterate over a sequence of numbers using the "range" and "xrange" functions. The difference between range and xrange is that the range function returns a new list with numbers of that specified range, whereas xrange returns an iterator, which is more efficient.


3 Answers

Maybe a for_first_then_each is what you're looking for? It takes your range in terms of iterators and applies the first function to the first element and the second function to the rest.

#include <iostream>
#include <vector>

template<typename BeginIt, typename EndIt, typename FirstFun, typename OthersFun>
void for_first_then_each(BeginIt begin, EndIt end, FirstFun firstFun, OthersFun othersFun) {
    if(begin == end) return;
    firstFun(*begin);
    for(auto it = std::next(begin); it != end; ++it) {
        othersFun(*it);
    };
} 

int main() {

    std::vector<int> v = {0, 1, 2, 3};

    for_first_then_each(v.begin(), v.end(),
        [](auto first) { std::cout << first + 42 << '\n'; },
        [](auto other) { std::cout << other - 42 << '\n'; }
    );

    // Outputs 42, -41, -40, -39

    return 0;
}
like image 78
Joakim Thorén Avatar answered Oct 19 '22 08:10

Joakim Thorén


You can't know which element you are visiting in a range based for loop unless you are looping over a container like an array or vector where you can take the address of the object and compare it to the address of the first item to figure out where in the container you are. You can also do this if the container provides lookup by value, you can see if the iterator returned from the find operation is the same as the begin iterator.

If you need special handling for the first element then you can fall back to a traditional for loop like

for (auto it = std::begin(items), first = it, end = std::end(items); it != end; ++it)
{
    if (it == first)
    {
        // do something
    }
    // Normal processing
}

If what you need to do can be factored out of the loop then you could use a range based for loop and just put the processing before the loop like

// do something
for(const auto &item: items)
{
    // Normal processing
}
like image 32
NathanOliver Avatar answered Oct 19 '22 10:10

NathanOliver


With Ranges coming in C++20, you can split this in two loops:

for (auto const& item : items | view::take(1)) {
    // first element only (or never executed if items is empty)
}

for (auto const& item : items | view::drop(1)) {
    // all after the first (or never executed if items has 1 item or fewer)
}

If you don't want to wait for C++20, check out range-v3 which supports both of these operations.

This won't work like this with an Input range (like if items is really a range that reads from cin) but will work just fine with any range that is Forward or better (I'm guessing items is a container here, so that should be fine).


A more straightforward version is actually to use enumerate (which only exists in range-v3, not in C++20):

for (auto const& [idx, item] : view::enumerate(items)) {
    if (idx == 0) {
         // first element only
    }
    // all elements
}
like image 3
Barry Avatar answered Oct 19 '22 09:10

Barry