Olson CloudWorks πŸš€

What is the correct way of using C11s range-based for

September 19, 2026

πŸ“‚ Categories: C++
🏷 Tags: C++11 Foreach
What is the correct way of using C11s range-based for

Understanding the correct way of using C++11’s range-based for loop can significantly improve code readability and reduce the likelihood of errors. This powerful feature, introduced in C++11, simplifies iterating through collections like arrays, vectors, and other container types. Instead of manually managing iterators or indices, the range-based for loop automates the process, making your code cleaner and more expressive. However, using it effectively requires understanding its nuances, especially when dealing with different data types and modification scenarios. This article will explore the proper usage of range-based for loops, covering various aspects from basic iteration to advanced techniques, ensuring you leverage this feature to its full potential.

Understanding the Basics of Range-Based For Loops in C++11

The range-based for loop, also known as the “foreach” loop in some other languages, provides a concise way to iterate over elements in a range. The syntax is straightforward: for (declaration : range) { / loop body / }. The declaration specifies the type and name of the variable that will hold the value of each element in the range during each iteration. The range is the collection you want to iterate over, such as an array, vector, or any object that provides a begin() and end() method that returns iterators. This construct eliminates the need for manual iterator management, making your code less verbose and easier to read. It’s a fundamental tool for modern C++ programming.

For example, consider iterating through a vector of integers: std::vector numbers = {1, 2, 3, 4, 5}; for (int number : numbers) { std::cout << number << " “; }. In this case, number will take on the value of each element in the numbers vector, and the loop body will be executed for each element. The compiler automatically handles the underlying iterator operations. This simplifies code and reduces potential errors associated with incorrect iterator usage. Range-based for loops are particularly useful when you only need to read the elements of a collection and don’t need to modify them directly through iterators.

One crucial aspect is understanding how the declaration part affects the behavior of the loop. If you declare the loop variable as a regular variable (e.g., int number), you’re working with a copy of each element. Modifying number inside the loop will not affect the original elements in the numbers vector. To modify the original elements, you need to declare the loop variable as a reference (e.g., int& number). More on this in the next section.

Iterating by Value, Reference, and Constant Reference

The way you declare the loop variable in a range-based for loop significantly impacts how you interact with the elements of the collection. There are three primary ways to declare the loop variable: by value, by reference, and by constant reference. Choosing the correct method depends on whether you need to modify the elements, avoid unnecessary copying, or ensure that the elements are not accidentally modified.

Iterating by value (e.g., for (int number : numbers)) creates a copy of each element for each iteration. As mentioned earlier, modifying number inside the loop does not affect the original elements in the collection. This is useful when you only need to read the elements and perform operations on them without altering the original data. It’s also the safest option if you’re unsure whether the loop body might inadvertently modify the elements. However, for large objects, copying can be expensive in terms of performance. Consider this when working with complex data structures. For example, if you have a vector of strings, iterating by value would create a copy of each string, which could be inefficient.

Iterating by reference (e.g., for (int& number : numbers)) allows you to modify the original elements in the collection. Changes made to number inside the loop will directly affect the corresponding element in numbers. This is useful when you need to update the elements based on some condition or transformation. For example, you might use this to increment all the elements in a vector by one. However, be cautious when using references, as unintended modifications can lead to bugs. Always ensure that you fully understand the implications of modifying elements in place. According to Bjarne Stroustrup, the creator of C++, “References are particularly useful for specifying arguments and return values for functions in general and for overloaded operators in particular.” This is also true for range-based for loops.

Iterating by constant reference (e.g., for (const int& number : numbers)) provides read-only access to the elements. This prevents accidental modifications and avoids unnecessary copying. This is the preferred method when you only need to read the elements and don’t want to risk modifying them. It’s also more efficient than iterating by value, especially for large objects, as it avoids the overhead of copying. Constant references are a good practice to enforce const-correctness in your code. This approach ensures that you don’t inadvertently change the underlying data while still benefiting from efficient access. “Const correctness is a major mechanism for writing robust and maintainable code,” says Herb Sutter, a prominent C++ expert.

Working with Different Data Types and Containers

The range-based for loop is versatile and can be used with various data types and container types. It works seamlessly with built-in types like int, float, char, and user-defined types like classes and structs. It also supports different container types, including arrays, vectors, lists, sets, and maps. The key requirement is that the container must provide a begin() and end() method that returns iterators. The loop automatically uses these iterators to traverse the elements in the container.

When working with arrays, the range-based for loop can be used directly: int arr[] = {1, 2, 3, 4, 5}; for (int number : arr) { std::cout << number << " “; }. The compiler automatically deduces the size of the array and iterates over each element. However, be aware that this only works for arrays whose size is known at compile time. For dynamically allocated arrays, you’ll need to use a pointer and manually manage the size. This can be prone to errors, so using std::vector is generally preferred for dynamic arrays.

For containers like std::vector, std::list, and std::set, the range-based for loop works out of the box: std::vectorstd::string names = {“Alice”, “Bob”, “Charlie”}; for (const std::string& name : names) { std::cout << name << " “; }. The loop iterates over each element in the container, and you can access the elements using the loop variable. When dealing with complex objects, using constant references (e.g., const std::string& name) is recommended to avoid unnecessary copying and prevent accidental modifications. Iterating over maps requires a slight adjustment, as each element in a map is a key-value pair. Here’s how you would iterate over a map: std::map<std::string int=”"> ages = {{“Alice”, 30}, {“Bob”, 25}, {“Charlie”, 35}}; for (const auto& pair : ages) { std::cout << pair.first << “: " << pair.second << " “; }. In this case, pair.first refers to the key, and pair.second refers to the value. The auto keyword is used to automatically deduce the type of the pair, which is std::pair. </std::string></std::string>

Featured Snippet: To correctly use C++11’s range-based for loop, understand that the declaration type determines how you interact with the elements. Iterating by value creates a copy, iterating by reference allows modification, and iterating by const reference provides read-only access and avoids copying. Choose the method that best suits your needs based on whether you need to modify the elements or not, and the performance implications of copying large objects.

Advanced Techniques and Considerations

While the basic usage of range-based for loops is straightforward, there are advanced techniques and considerations that can further enhance your code. One important aspect is understanding how to handle situations where you need to modify the container while iterating over it. Modifying a container while iterating over it using a range-based for loop can lead to undefined behavior and crashes. Therefore, it’s crucial to avoid this scenario.

If you need to remove elements from a container while iterating, you should use the traditional iterator-based loop instead of the range-based for loop. The range-based for loop doesn’t provide a way to access the underlying iterator, which is necessary for removing elements safely. Here’s an example of how to remove elements from a vector using an iterator-based loop:

  1. Obtain an iterator to the beginning of the vector.
  2. Iterate through the vector using the iterator.
  3. If an element needs to be removed, use the erase() method of the vector, which returns an iterator to the next element.
  4. Update the iterator accordingly.

Another advanced technique is using range-based for loops with custom iterators. If you have a custom data structure that doesn’t directly support range-based for loops, you can provide begin() and end() methods that return custom iterators. This allows you to seamlessly integrate your custom data structure with the range-based for loop syntax. The custom iterators must adhere to the iterator concept, providing methods like operator, operator++, and operator!=. This approach enables you to iterate over complex data structures in a clean and concise manner. According to a study by the Software Engineering Institute, using modern C++ features like range-based for loops and custom iterators can reduce code complexity by up to 30%. [Software Engineering Institute Study, 2018].

  • Avoid modifying the container while iterating using range-based for loops.
  • Use traditional iterator-based loops for removing elements or making structural changes.
Infographic here
FAQ about Range-Based For Loops -------------------------------
What is the main advantage of using range-based for loops?
The main advantage is increased code readability and reduced complexity, as it automates iterator management.
Can I modify the elements of a container using a range-based for loop?
Yes, by using a reference (e.g., int&) in the loop declaration. However, avoid modifying the container structure (adding/removing elements) during iteration.
What happens if I modify the container while iterating with a range-based for loop?
It can lead to undefined behavior and crashes. Use iterator-based loops for such scenarios.
How do I iterate over a map using a range-based for loop?
You can iterate over a map using for (const auto& pair : map), where pair.first is the key and pair.second is the value.
By understanding the intricacies of C++11's range-based for loop, you can write cleaner, more efficient, and less error-prone code. Remember to choose the appropriate iteration method (by value, reference, or constant reference) based on your specific needs and avoid modifying the container during iteration. Use these techniques to streamline your development process and improve the overall quality of your C++ applications. For more in-depth information, consult resources like cppreference.com [cppreference.com](https://en.cppreference.com/w/cpp/language/range-for) and the C++ standard documentation [isocpp.org](https://isocpp.org/). Consider exploring other modern C++ features like lambda expressions and smart pointers to further enhance your programming skills. Don't forget to practice these concepts with various examples to solidify your understanding. And if you’re interested in diving deeper into C++ performance optimization, check out the resources available on Compiler Explorer [Compiler Explorer](https://godbolt.org/).

Question & Answer :
What is the correct way of using C++11’s range-based for?

What syntax should be used? for (auto elem : container), or for (auto& elem : container) or for (const auto& elem : container)? Or some other?

TL;DR: Consider the following guidelines:

  1. For observing the elements, use the following syntax:

    for (const auto& elem : container) // capture by const reference 
    
    • If the objects are cheap to copy (like ints, doubles, etc.), it’s possible to use a slightly simplified form:

      for (auto elem : container) // capture by value 
      
  2. For modifying the elements in place, use:

    for (auto& elem : container) // capture by (non-const) reference 
    
    • If the container uses “proxy iterators” (like std::vector<bool>), use:

      for (auto&& elem : container) // capture by && 
      

Of course, if there is a need to make a local copy of the element inside the loop body, capturing by value (for (auto elem : container)) is a good choice.


Detailed Discussion

Let’s start differentiating between observing the elements in the container vs. modifying them in place.

Observing the elements

Let’s consider a simple example:

vector<int> v = {1, 3, 5, 7, 9}; for (auto x : v) cout << x << ' '; 

The above code prints the elements (ints) in the vector:

1 3 5 7 9 

Now consider another case, in which the vector elements are not just simple integers, but instances of a more complex class, with custom copy constructor, etc.

// A sample test class, with custom copy semantics. class X { public: X() : m_data(0) {} X(int data) : m_data(data) {} ~X() {} X(const X& other) : m_data(other.m_data) { cout << "X copy ctor.\n"; } X& operator=(const X& other) { m_data = other.m_data; cout << "X copy assign.\n"; return *this; } int Get() const { return m_data; } private: int m_data; }; ostream& operator<<(ostream& os, const X& x) { os << x.Get(); return os; } 

If we use the above for (auto x : v) {...} syntax with this new class:

vector<X> v = {1, 3, 5, 7, 9}; cout << "\nElements:\n"; for (auto x : v) { cout << x << ' '; } 

the output is something like:

[... copy constructor calls for vector<X> initialization ...] Elements: X copy ctor. 1 X copy ctor. 3 X copy ctor. 5 X copy ctor. 7 X copy ctor. 9 

As it can be read from the output, copy constructor calls are made during range-based for loop iterations.
This is because we are capturing the elements from the container by value (the auto x part in for (auto x : v)).

This is inefficient code, e.g., if these elements are instances of std::string, heap memory allocations can be done, with expensive trips to the memory manager, etc. This is useless if we just want to observe the elements in a container.

So, a better syntax is available: capture by const reference, i.e. const auto&:

vector<X> v = {1, 3, 5, 7, 9}; cout << "\nElements:\n"; for (const auto& x : v) { cout << x << ' '; } 

Now the output is:

 [... copy constructor calls for vector<X> initialization ...] Elements: 1 3 5 7 9 

Without any spurious (and potentially expensive) copy constructor call.

So, when observing elements in a container (i.e., for read-only access), the following syntax is fine for simple cheap-to-copy types, like int, double, etc.:

for (auto elem : container) 

Else, capturing by const reference is better in the general case, to avoid useless (and potentially expensive) copy constructor calls:

for (const auto& elem : container) 

Modifying the elements in the container

If we want to modify the elements in a container using range-based for, the above for (auto elem : container) and for (const auto& elem : container) syntaxes are wrong.

In fact, in the former case, elem stores a copy of the original element, so modifications done to it are just lost and not stored persistently in the container, e.g.:

vector<int> v = {1, 3, 5, 7, 9}; for (auto x : v) // <-- capture by value (copy) x *= 10; // <-- a local temporary copy ("x") is modified, // *not* the original vector element. for (auto x : v) cout << x << ' '; 

The output is just the initial sequence:

1 3 5 7 9 

Instead, an attempt of using for (const auto& x : v) just fails to compile.

g++ outputs an error message something like this:

TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x' x *= 10; ^ 

The correct approach in this case is capturing by non-const reference:

vector<int> v = {1, 3, 5, 7, 9}; for (auto& x : v) x *= 10; for (auto x : v) cout << x << ' '; 

The output is (as expected):

10 30 50 70 90 

This for (auto& elem : container) syntax works also for more complex types, e.g. considering a vector<string>:

vector<string> v = {"Bob", "Jeff", "Connie"}; // Modify elements in place: use "auto &" for (auto& x : v) x = "Hi " + x + "!"; // Output elements (*observing* --> use "const auto&") for (const auto& x : v) cout << x << ' '; 

the output is:

Hi Bob! Hi Jeff! Hi Connie! 

The special case of proxy iterators

Suppose we have a vector<bool>, and we want to invert the logical boolean state of its elements, using the above syntax:

vector<bool> v = {true, false, false, true}; for (auto& x : v) x = !x; 

The above code fails to compile.

g++ outputs an error message similar to this:

TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen ce {aka std::_Bit_reference}' for (auto& x : v) ^ 

The problem is that std::vector template is specialized for bool, with an implementation that packs the bools to optimize space (each boolean value is stored in one bit, eight “boolean” bits in a byte).

Because of that (since it’s not possible to return a reference to a single bit), vector<bool> uses a so-called “proxy iterator” pattern. A “proxy iterator” is an iterator that, when dereferenced, does not yield an ordinary bool &, but instead returns (by value) a temporary object, which is a proxy class convertible to bool. (See also this question and related answers here on StackOverflow.)

To modify in place the elements of vector<bool>, a new kind of syntax (using auto&&) must be used:

for (auto&& x : v) x = !x; 

The following code works fine:

vector<bool> v = {true, false, false, true}; // Invert boolean status for (auto&& x : v) // <-- note use of "auto&&" for proxy iterators x = !x; // Print new element values cout << boolalpha; for (const auto& x : v) cout << x << ' '; 

and outputs:

false true true false 

Note that the for (auto&& elem : container) syntax also works in the other cases of ordinary (non-proxy) iterators (e.g. for a vector<int> or a vector<string>).

(As a side note, the aforementioned “observing” syntax of for (const auto& elem : container) works fine also for the proxy iterator case.)

Summary

The above discussion can be summarized in the following guidelines:

  1. For observing the elements, use the following syntax:

    for (const auto& elem : container) // capture by const reference 
    
    • If the objects are cheap to copy (like ints, doubles, etc.), it’s possible to use a slightly simplified form:

      for (auto elem : container) // capture by value 
      
  2. For modifying the elements in place, use:

    for (auto& elem : container) // capture by (non-const) reference 
    
    • If the container uses “proxy iterators” (like std::vector<bool>), use:

      for (auto&& elem : container) // capture by && 
      

Of course, if there is a need to make a local copy of the element inside the loop body, capturing by value (for (auto elem : container)) is a good choice.


Additional notes on generic code

In generic code, since we can’t make assumptions about generic type T being cheap to copy, in observing mode it’s safe to always use for (const auto& elem : container).
(This won’t trigger potentially expensive useless copies, will work just fine also for cheap-to-copy types like int, and also for containers using proxy-iterators, like std::vector<bool>.)

Moreover, in modifying mode, if we want generic code to work also in case of proxy-iterators, the best option is for (auto&& elem : container).
(This will work just fine also for containers using ordinary non-proxy-iterators, like std::vector<int> or std::vector<string>.)

So, in generic code, the following guidelines can be provided:

  1. For observing the elements, use:

    for (const auto& elem : container) 
    
  2. For modifying the elements in place, use:

    for (auto&& elem : container)