26

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".

honk
  • 9,137
  • 11
  • 75
  • 83
bpeikes
  • 3,495
  • 9
  • 42
  • 80
  • That's why I'm sad that `bool`s don't have `--` anymore... – Quentin Feb 22 '19 at 17:09
  • Shouldn't it be `else { /* Normal processing */ }`? In this case you can look at this answer (it skips the last element, the idea is similar): https://stackoverflow.com/a/35372958/2956272 –  Feb 22 '19 at 17:14
  • 2
    When my code requires special first-element action (which frankly, I could likely count on one hand the number of times I've needed that in the last five years and still have fingers left over), I don't use ranged-for. I use an iterator, and if-test, and a nested while loop thereafter. The only place that causes headache is when the action taken is a precursor and not an alternative (i.e. the model you're using is such a case: first element gets actions A+B, remaining elements get B, as opposed to first element gets A, remaining get B). – WhozCraig Feb 22 '19 at 17:15
  • 6
    Possible duplicate of [Skipping in Range-based for based on 'index'?](https://stackoverflow.com/questions/21215947/skipping-in-range-based-for-based-on-index) – hlscalon Feb 22 '19 at 17:17
  • Whoops, typo! `if(isFirst)...` – TonyK Feb 22 '19 at 17:43
  • 1
    Thanks everyone. I think the a @Ali who posted on https://stackoverflow.com/questions/21215947/skipping-in-range-based-for-based-on-index, had great points. Moving the special case outside of the loop is a much better idea. – bpeikes Feb 22 '19 at 20:48

8 Answers8

15

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;
}
Joakim Thorén
  • 1,111
  • 10
  • 17
  • 3
    I like this the best. It's the most efficient as well as you are not doing a check in the loop. – bpeikes Mar 08 '19 at 05:04
8

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
}
scohe001
  • 15,110
  • 2
  • 31
  • 51
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 1
    Your first example could be nicely wrapped into a (algorithm) template taking an iterator range and two `std::functions` (FirstElementProcessor,, OtherElementProcessor). – πάντα ῥεῖ Feb 22 '19 at 17:19
  • I've thought a template, but I was wondering if there was anything already like this, since it seems to be a common pattern. – bpeikes Feb 22 '19 at 17:33
4

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
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • I’m a fan of unrolling the loop. ie Get begin, check if its not end, if so process first item, increment iterator then finish the iteration. Wrap the whole thing in a template function. – bpeikes Mar 12 '19 at 21:03
  • @bpeikes Sure - that answer exists. This is just an alternative approach. Has some downsides, has some benefits (e.g. you can `return` from the first loop, `break` from the second). – Barry Mar 12 '19 at 21:06
3

Check the object address to see if it's the first item:

for(const auto &item: items)
{
    if (&item != &(*items.begin())
    { 
       // do something for all but the first
    }
    // Normal processing
}
moodboom
  • 6,225
  • 2
  • 41
  • 45
  • 2
    If not a thing of beauty, I find this the least offensive solution. I want to say "if(/*not first iteration*/)", and this is close. A brief comment saves the reader from needing much expertise. My comma placements thank you! – Ron Burk Nov 07 '20 at 07:04
2

A fun alternative solution, that I would not use in production without great care, would be to use custom iterator.

int main() {
  std::vector<int> v{1,2,3,4};

  for (const auto & [is_first,b] : wrap(v)) {
    if (is_first) {
      std::cout << "First: ";
    }
    std::cout << b << std::endl;
  }
}

A toy implementation could look like this:

template<typename T>
struct collection_wrap {
  collection_wrap(T &c): c_(c) {}

  struct magic_iterator {
    bool is_first = false;
    typename T::iterator itr;

    auto operator*() {
      return std::make_tuple(is_first, *itr);
    }

    magic_iterator operator++() {
      magic_iterator self = *this;
      itr++;
      //only works for forward
      is_first = false;
      return self;
    }

    bool operator!=(const magic_iterator &o) {
      return itr != o.itr;
    }
  };

  magic_iterator begin() {
    magic_iterator itr;
    itr.is_first = true;
    itr.itr = c_.begin();

    return itr;
  }

  magic_iterator end() {
    magic_iterator itr;
    itr.is_first = false;
    itr.itr = c_.end();

    return itr;
  }


  T &c_;
};

template<typename Collection>
collection_wrap<Collection>
wrap(Collection &vec) {
  return collection_wrap(vec);
}
Xaqq
  • 4,308
  • 2
  • 25
  • 38
1

Since C++20, you can slightly improve your range-based for loop by using an init-statement. The init-statement allows you to move your isFirst flag into the scope of the loop so that this flag is no longer visible outside the loop:

std::vector<int> items { 1, 2, 3 };

for(bool isFirst(true); const auto &item: items) {
    if(!isFirst) {
        std::cout << "Do something with: " << item << std::endl;
    }

    std::cout << "Normal processing: " << item << std::endl;
    isFirst = false;
}

Output:

Normal processing: 1
Do something with: 2
Normal processing: 2
Do something with: 3
Normal processing: 3

Code on Wandbox

honk
  • 9,137
  • 11
  • 75
  • 83
0

An approach still valid in C++ is to use a macro:

#include <iostream>
#include <vector>

#define FOR(index, element, collection, body) { \
    auto &&col = collection; \
    typeof(col.size()) index = 0; \
    for(auto it=col.begin(); it!=col.end(); index++, it++) { \
        const auto &element = *it; \
        body; \
    } \
}

using namespace std;

int main() {
    vector<int> a{0, 1, 2, 3};
    FOR(i, e, a, {
        if(i) cout << ", ";
        cout << e;
    })
    cout << endl;

    FOR(i, e, vector<int>({0, 1, 2, 3}), {
        if(i) cout << ", ";
        cout << e;
    })
    cout << endl;

    return 0;
}

Prints:

0, 1, 2, 3
0, 1, 2, 3

This solution is succinct compared to alternative options. On the downside, index is being tested and incremented on each iteration of the loop - this can be avoided by increasing the complexity of the macro and by using bool first instead of index, but using index in the macro covers more use cases than bool first.

atomsymbol
  • 370
  • 8
  • 11
  • This is a perfect case for a template function, why would you go with a macro, which can have all sorts of edge case failures? – bpeikes Mar 12 '19 at 20:59
  • @bpeikes It is a valid approach. That is all. No need to use it if you don't want to. – atomsymbol Mar 13 '19 at 02:00
  • @bpeikes Without C++ concepts, templates are to some extent based on duck typing like macros. That written, I am *not* advocating use of macros over templates. – atomsymbol Mar 13 '19 at 02:03
-2

I assume you want to know how to retrieve the first element, you could do this with an array and a vector.

I'm going to show the array here.

First include this in your code:

#include <array>

Then convert your array accordingly:

    std::array<std::string, 4> items={"test1", "test2", "test3", "test4"};

    for(const auto &item: items)
    {
        if(item == items.front()){
           // do something     
           printf("\nFirst: %s\n", item.c_str()); //or simply printf("\nFirst:"); since you gonna output a double element
        }
        // Normal processing
           printf("Item: %s\n", item.c_str());
    }
    return 0;
}
Secko
  • 7,664
  • 5
  • 31
  • 37
  • 2
    Why are you using `printf()` in c++ code? _"Performance"_ cargo culting? – πάντα ῥεῖ Feb 22 '19 at 17:48
  • 5
    @πάνταῥεῖ: I use `printf` all the time in my C++ code. I'm allowed to, and I prefer it, and it works, so who are you to judge me? – TonyK Feb 22 '19 at 17:49
  • 1
    Not all items containers usable with a range based for loop necessarily implement an access to front() – Dmytro Dadyka Feb 22 '19 at 17:52
  • @πάνταῥεῖ Yeah, I like printf more, what can you do. Didn't know people downvoted for that. :D – Secko Feb 22 '19 at 18:09
  • I didn't downvote, and certainly not for that. I've just been asking, because it's unusual to see in clean c++ code. Also _cargo cult programming_ is a well established term in modern software development. – πάντα ῥεῖ Feb 22 '19 at 18:12
  • @πάνταῥεῖ Yeah, I understand, I just like some of the things from C better, still use them in C++. You can also try execution time of printf and std::cout and see the differences. I assume the std::cout is more optimized in C++ for int and char, probably more then printf is (in C++). As far as the downvote, it was a joke. Did I forget a wink in there. ;) – Secko Feb 22 '19 at 18:15
  • @DmytroDadyka what do you mean? The four elements are valid with std::array, or did I misunderstand? – Secko Feb 22 '19 at 18:20
  • @Secko, I mean, that the author don't say anything about `std::array`. The `items` doesn't necessarily have a `front()` method. Look please on my answer, It is almost identical to yours, but is correct. – Dmytro Dadyka Feb 22 '19 at 19:30
  • 3
    @Secko, the downvote is not a joke. You compare values `item == items.front()`. There is no guarantee that the first element does not repeat! – Dmytro Dadyka Feb 22 '19 at 20:01
  • @DmytroDadyka Ah I see, you are right there. The joke was for printf not for the whole answer. Thanks for telling me about the bug though. – Secko Feb 22 '19 at 20:02