Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Simplified Iteration with Range-Based For
Iterate over container elements cleanly without managing indices.
Range-based for loops (for-each)
Traditional for loops work well for array traversal, but they're verbose, require index management, and are prone to off-by-one errors. C++ offers a cleaner alternative: the range-based for loop.
Traditional loop limitations
Here's a standard loop iterating through a vector:
#include <iostream>
#include <vector>
int main()
{
std::vector scores{ 85, 92, 78, 90, 88 };
for (std::size_t idx{ 0 }; idx < scores.size(); ++idx)
std::cout << scores[idx] << ' ';
std::cout << '\n';
return 0;
}
This requires declaring the index variable, checking bounds, incrementing correctly, and indexing the vector—all opportunities for bugs.
Introducing range-based for loops
Range-based for loops (also called for-each loops) simplify traversal:
for (element_declaration : container)
statement;
The loop iterates through each element in container, assigning the current element to the variable declared in element_declaration, then executes statement.
Here's the previous example rewritten:
#include <iostream>
#include <vector>
int main()
{
std::vector scores{ 85, 92, 78, 90, 88 };
for (int score : scores)
std::cout << score << ' ';
std::cout << '\n';
return 0;
}
Much cleaner! No indices, no size checks, no manual incrementing. The loop automatically iterates through all elements.
Core Understanding: The declared element (score) receives the value of each array element, not an index. For each iteration, the current element is copied into score.
Best practice: Favor range-based for loops over traditional for loops when traversing containers.
Range-based loops with empty containers
If the container is empty, the loop body simply doesn't execute:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> empty{};
for (int val : empty)
std::cout << "This never prints\n";
std::cout << "Loop completed\n";
return 0;
}
Output: Loop completed
Using auto for type deduction
Since the element declaration should match the element type, auto is perfect here:
#include <iostream>
#include <vector>
int main()
{
std::vector scores{ 85, 92, 78, 90, 88 };
for (auto score : scores) // Compiler deduces int
std::cout << score << ' ';
std::cout << '\n';
return 0;
}
Benefits:
- No type redundancy
- Automatically adapts if element type changes
- Prevents accidental type mismatches and conversions
Best practice: Use auto with range-based for loops to have the compiler deduce the element type.
Avoiding copies with references
Consider iterating over a vector of strings:
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> names{ "Alice", "Bob", "Charlie", "Diana" };
for (auto name : names) // Copies each string!
std::cout << name << ' ';
std::cout << '\n';
return 0;
}
Each iteration copies an entire string—expensive! Use a reference instead:
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> names{ "Alice", "Bob", "Charlie", "Diana" };
for (const auto& name : names) // No copying
std::cout << name << ' ';
std::cout << '\n';
return 0;
}
The reference binds directly to each element, avoiding copies. Use const when you don't need to modify elements.
If you need to modify elements, omit const:
#include <iostream>
#include <vector>
int main()
{
std::vector damage{ 10, 20, 30, 40 };
for (auto& dmg : damage)
dmg *= 2; // Double each value
for (auto dmg : damage)
std::cout << dmg << ' '; // Prints: 20 40 60 80
return 0;
}
Choosing between auto, auto&, and const auto&
General guidelines:
- Use
autowhen working with cheap-to-copy types (likeint) and copies are acceptable - Use
auto&when you need to modify elements - Use
const auto&for expensive-to-copy types or when you want to guarantee no modifications
However, many developers prefer always using const auto& for range-based loops because it's more future-proof. If the element type later changes from cheap-to-copy to expensive-to-copy, const auto& continues working efficiently.
Best practice: For range-based for loops, prefer:
autowhen modifying copiesauto&when modifying original elementsconst auto&when just viewing elements (most common)
Range-based loops with other container types
Range-based for loops work with many container types, including std::array, C-style arrays, strings, and various other standard containers:
#include <array>
#include <iostream>
int main()
{
std::array levels{ 5, 12, 8, 15, 20 };
for (const auto& level : levels)
std::cout << level << ' ';
std::cout << '\n';
return 0;
}
Limitations of range-based for loops
Range-based for loops don't directly provide element indices. If you need indices, use a traditional loop or manually maintain a counter:
#include <iostream>
#include <vector>
int main()
{
std::vector inventory{ "sword", "shield", "potion" };
int idx{ 0 };
for (const auto& item : inventory)
{
std::cout << idx << ": " << item << '\n';
++idx;
}
return 0;
}
Though if you need indices, consider whether a traditional loop might be clearer.
Iterating in reverse (C++20)
Use std::views::reverse to iterate backwards:
#include <iostream>
#include <ranges>
#include <vector>
int main()
{
std::vector ranks{ 1, 2, 3, 4, 5 };
for (const auto& rank : std::views::reverse(ranks))
std::cout << rank << ' '; // Prints: 5 4 3 2 1
std::cout << '\n';
return 0;
}
Simplified Iteration with Range-Based For - Quiz
Test your understanding of the lesson.
Practice Exercises
Range-Based For Loops
Practice using range-based for loops to iterate over containers. Learn when to use value, reference, and const reference iteration.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!