Avoiding if statement inside a for loop?
Pass in the body of the loop as a functor. It gets inlined at compile-time, no performance penalty.
The idea of passing in what varies is ubiquitous in the C++ Standard Library. It is called the strategy pattern.
If you are allowed to use C++11, you can do something like this:
#include <iostream>
#include <set>
#include <vector>
template <typename Container, typename Functor, typename Index = std::size_t>
void for_each_indexed(const Container& c, Functor f, Index index = 0) {
for (const auto& e : c)
f(index++, e);
}
int main() {
using namespace std;
set<char> s{'b', 'a', 'c'};
// indices starting at 1 instead of 0
for_each_indexed(s, [](size_t i, char e) { cout<<i<<'\t'<<e<<'\n'; }, 1u);
cout << "-----" << endl;
vector<int> v{77, 88, 99};
// without index
for_each_indexed(v, [](size_t , int e) { cout<<e<<'\n'; });
}
This code is not perfect but you get the idea.
In old C++98 it looks like this:
#include <iostream>
#include <vector>
using namespace std;
struct with_index {
void operator()(ostream& out, vector<int>::size_type i, int e) {
out << i << '\t' << e << '\n';
}
};
struct without_index {
void operator()(ostream& out, vector<int>::size_type i, int e) {
out << e << '\n';
}
};
template <typename Func>
void writeVector(const vector<int>& v, Func f) {
for (vector<int>::size_type i=0; i<v.size(); ++i) {
f(cout, i, v[i]);
}
}
int main() {
vector<int> v;
v.push_back(77);
v.push_back(88);
v.push_back(99);
writeVector(v, with_index());
cout << "-----" << endl;
writeVector(v, without_index());
return 0;
}
Again, the code is far from perfect but it gives you the idea.
To expand on Ali's answer, which is perfectly correct but still duplicates some code (part of the loop body, this is unfortunately hardly avoidable when using the strategy pattern)...
Granted in this particular case the code duplication is not much but there's a way to reduce it even more, which comes in handy if the function body is bigger than just a few instructions.
The key is to use the compiler's ability to perform constant folding / dead code elimination. We can do that by manually mapping the runtime value of index
to a compile-time value (easy to do when there are only a limited number of cases -- two in this case) and use a non-type template argument which is known at compile-time:
template<bool index = true>
// ^^^^^^ note: the default value is now part of the template version
// see below to understand why
void writeVector(const vector<int>& vec) {
for (size_t i = 0; i < vec.size(); ++i) {
if (index) { // compile-time constant: this test will always be eliminated
cout << i << "\t"; // this will only be kept if "index" is true
}
cout << vec[i] << "\n";
}
}
void writeVector(const vector<int>& vec, bool index)
// ^^^^^ note: no more default value, otherwise
// it would clash with the template overload
{
if (index) // runtime decision
writeVector<true>(vec);
// ^^^^ map it to a compile-time constant
else
writeVector<false>(vec);
}
This way we end up with compiled code which is equivalent to your second code example (outer if
/ inner for
) but without duplicating the code ourselves. Now we can make the template version of writeVector
as complicated as we want, there will always be a single piece of code to maintain.
Note how the template version (which takes a compile-time constant in the form of a non-type template argument) and the non-template version (which takes a runtime variable as a function argument) are overloaded. This allows you to choose the most relevant version depending on your needs, having a rather similar, easy to remember syntax in both cases:
writeVector<true>(vec); // you already know at compile-time which version you want
// no need to go through the non-template runtime dispatching
writeVector(vec, index); // you don't know at compile-time what "index" will be
// so you have to use the non-template runtime dispatching
writeVector(vec); // you can even use your previous syntax using a default argument
// it will call the template overload directly
In the function, I'm doing the if (index) check on every round of my for-loop, even though the result is always the same. This is against "worrying about the performance".
If this is indeed the case, the branch predictor will have no problem in predicting the (constant) result. As such, this will only cause a mild overhead for mispredictions in the first few iterations. It's nothing to worry about in terms of performance
In this case I advocate for keeping the test inside the loop for clarity.