SimplifyC++ Article
Comparison stdfor_each vs. Range‑based for Loop
Comparison: std::for_each vs. Range‑based for Loop
Both std::for_each (from <algorithm>) and the range‑based for loop (C++11 and later) iterate over every element of a container. However, they have different strengths, syntax, and ideal use cases. Since your article covers <functional>, std::for_each is especially relevant because it shines when you already have a callable object (like a functor from <functional>) that you want to apply.
1. Basic Syntax
Range‑based for | std::for_each |
|---|---|
for (int c : values) { doSomething(c); } | std::for_each(values.begin(), values.end(), doSomething); |
| Clean, built‑in language feature. | Algorithm from <algorithm>. Works with any iterator pair. |
2. When to Use Each
Use range‑based for when… | Use std::for_each when… |
|---|---|
| You need a simple, readable loop for most cases. | You already have a named function/functor (e.g., std::function, std::mem_fn, std::plus). |
You want to break early (break;) or skip (continue;). | You want to compose operations with std::bind or use parallel execution (C++17). |
You need to modify the loop variable inside (e.g., for (auto& x : vec)). | You’re writing generic code that should work with any iterator pair (not just containers). |
| Performance is critical and you want zero overhead (compilers optimise both equally well). | You want to combine with other algorithms (e.g., std::transform, std::accumulate) in a functional pipeline. |
3. Detailed Comparison Table
| Feature | Range‑based for | std::for_each |
|---|---|---|
| Readability | Excellent for simple iteration. | Slightly more verbose, but expressive when using named functions. |
| Early exit | break, continue, return (inside function) | No – must throw exception or use std::find_if etc. |
| Iterator flexibility | Works only on containers with begin()/end(). | Works on any iterator pair (e.g., raw array, stream iterators). |
| Parallel execution (C++17) | Not available. | Yes: std::for_each(std::execution::par, …) |
| Return value | None (statement, not expression). | Returns the function object (useful for state). |
Use with std::function | You can call a std::function inside the body. | Directly accepts any callable, including std::function. |
| Performance | Identical (both are zero‑overhead abstractions). | Identical (both inline to same machine code). |
4. Examples Side‑by‑Side
Simple transformation (print each element)
std::vector<int> values = {1, 2, 3, 4, 5};
// Range‑based forfor (int x : values) { std::cout << x << ' ';}
// std::for_eachstd::for_each(values.begin(), values.end(), [](int x) { std::cout << x << ' '; });
Using a predefined functor from <functional>
void increment(int& n) { ++n; }
// Range‑based forfor (int& x : values) { increment(x);}
// std::for_each – cleaner when you already have the functionstd::for_each(values.begin(), values.end(), increment);
Using std::mem_fn with std::for_each
struct Person { void greet() const; };std::vector<Person> people;
// Range‑based forfor (const auto& p : people) { p.greet();}
// std::for_each – very concisestd::for_each(people.begin(), people.end(), std::mem_fn(&Person::greet));
Parallel execution (C++17)
// Range‑based for cannot be parallelised directly.// std::for_each can:std::for_each(std::execution::par, values.begin(), values.end(), [](int& x) { x *= 2; });
5. Returning a Stateful Function Object
std::for_each returns the callable after processing. This is useful for accumulating state.
struct Sum { int total = 0; void operator()(int x) { total += x; }};
Sum s = std::for_each(values.begin(), values.end(), Sum());std::cout << "Sum = " << s.total << '\n';
With a range‑based for, you would need an external accumulator variable:
int total = 0;for (int x : values) total += x;// total holds the sum
Both work, but std::for_each encapsulates the state inside the functor.
6. Performance
Both constructs compile to identical assembly for simple operations. There is no performance penalty for using std::for_each – it’s just a template that inlines the loop. The range‑based for is also syntactic sugar for a traditional iterator loop.
Example compiled with -O2 (both produce the same loop):
// Range‑based forfor (int x : values) sum += x;
// std::for_eachstd::for_each(values.begin(), values.end(), [&](int x){ sum += x; });
7. Recommendation Summary
Prefer range‑based
forfor most day‑to‑day loops – it’s simpler, supportsbreak, and is very readable.Use
std::for_eachwhen:You already have a callable object (e.g., from
<functional>) and want to apply it directly.You need parallel execution.
You are writing generic code that works with arbitrary iterator pairs.
You want to use the return value (stateful functor).
You are working in a “functional” style (pipeline of algorithms).
8. Integration with <functional>
std::for_each is a natural partner for <functional> because it directly accepts:
std::functionobjects,results of
std::bind,std::mem_fnwrappers,built‑in functors like
std::plus,std::greater(though those are unary/binary, so you’d needstd::bindor a lambda to adapt them).
Example:
std::vector<int> v = {1,2,3};std::for_each(v.begin(), v.end(), std::bind(std::multiplies<int>(), std::placeholders::_1, 2));// multiplies each element by 2
But in modern C++, a lambda is often clearer than std::bind. Still, std::for_each remains a valid choice.
Final verdict: Both are excellent tools. Know both and choose the one that makes your code most expressive and maintainable. For teaching <functional>, std::for_each provides a perfect stage to demonstrate callable objects.
What did you think?
Sign in to react or comment.
Comments
0No comments yet.