C++ range based loop with special case for first item?
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;
}
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
}
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);
}