Range-Based For Loop Patterns
The range-based `for` loop (C++11) is the idiomatic way to iterate any container or any type with `begin()` / `end()`. This snippet covers the three reference flavours (by value, by const reference, by mutable reference), iterating over arrays and `std::initializer_list`, and how to also access the index when you need it. Get the reference forms right and you avoid both copies and accidental mutations.
550 views
2
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> words = {"alpha", "beta", "gamma"};
// BAD: by value copies each std::string into the loop variable.
// for (std::string w : words) std::cout << w << "\n";
// GOOD: const reference reads without copying and prevents mutation.
for (const std::string& w : words) std::cout << w << "\n";
// For trivially copyable types like int, by value is fine.
std::vector<int> nums = {1, 2, 3};
for (int n : nums) std::cout << n << " ";
std::cout << "\n";
return 0;
}Take by const T& for any non-trivial element type to skip the copy and document read-only intent. For built-in types like int, double, or pointers, by value is just as fast and reads cleaner. The classic mistake is for (auto x : v) over a vector<string>: it silently copies every element, which can dominate the loop's runtime on a large vector of strings. Make auto a default but pair it with & (for (const auto& x : v)) for the same reason.
#include <iostream>
#include <vector>
void demoMutate() {
std::vector<int> nums = {1, 2, 3, 4};
for (int& n : nums) n *= 2;
for (int n : nums) std::cout << n << " ";
std::cout << "\n"; // 2 4 6 8
}
int main() {
demoMutate();
return 0;
}A non-const reference (int&) lets you write through the loop variable, modifying the underlying container in place. This is shorter than the equivalent indexed loop and harder to get wrong: there is no i to forget to advance. Use it for transforms like normalisation, scaling, or in-place clamping. Be careful with auto& over a std::vector<bool> because it returns proxy references; that is one of the few cases where the indexed form is actually safer.
#include <iostream>
#include <vector>
#include <string>
#include <cstddef>
void demoIndex() {
std::vector<std::string> labels = {"red", "green", "blue"};
std::size_t i = 0;
for (const auto& s : labels) {
std::cout << i++ << ": " << s << "\n";
}
// Plain C-style array also works with range-for in C++11+.
int arr[] = {10, 20, 30};
int sum = 0;
for (int x : arr) sum += x;
std::cout << "sum=" << sum << "\n";
}
int main() {
demoIndex();
return 0;
}When you need the index alongside the value, the cleanest pre-C++20 form is a side counter that you increment inside the body. C++20's std::ranges::views::enumerate is even nicer but is not available on the GCC 9.2 / C++17 compiler used here. Range-for also works on plain C arrays of known size, no sizeof(arr)/sizeof(arr[0]) ritual needed, because the array decays gracefully into the begin/end machinery. The same loop form works on std::initializer_list, std::array, and any user type that implements begin() and end().
