HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppCritical

What is the correct way of using C++11's range-based for?

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
correcttheusingforwayrangewhatbased

Problem

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?

Solution

TL;DR: Consider the following guidelines:

-
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


-
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), 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 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 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 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 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 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 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 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:

vector 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 << ' ';


t

Code Snippets

for (const auto& elem : container)    // capture by const reference
for (auto elem : container)    // capture by value
for (auto& elem : container)    // capture by (non-const) reference
for (auto&& elem : container)    // capture by &&
vector<int> v = {1, 3, 5, 7, 9};

for (auto x : v)
    cout << x << ' ';

Context

Stack Overflow Q#15927033, score: 497

Revisions (0)

No revisions yet.