patterncppCritical
What is the correct way of using C++11's range-based for?
Viewed 0 times
correcttheusingforwayrangewhatbased
Problem
What is the correct way of using C++11's range-based
What syntax should be used?
or
Or some other?
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:
-
If the objects are cheap to copy (like
it's possible to use a slightly simplified form:
-
For modifying the elements in place, use:
-
If the container uses "proxy iterators" (like
Of course, if there is a need to make a local copy of the element inside the loop body, capturing by value (
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:
The above code prints the elements (
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.
If we use the above
the output is something like:
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
This is inefficient code, e.g., if these elements are instances of
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
Now the output is:
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
Else, capturing by
to avoid useless (and potentially expensive) copy constructor calls:
Modifying the elements in the container
If we want to modify the elements in a container using range-based
the above
syntaxes are wrong.
In fact, in the former case,
element, so modifications done to it are just lost and not stored persistently
in the container, e.g.:
The output is just the initial sequence:
Instead, an attempt of using
g++ outputs an error message something like this:
The correct approach in this case is capturing by non-
The output is (as expected):
This
e.g. considering a
t
-
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 9Now 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.
9As 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 9Without 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 originalelement, 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 9Instead, 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 90This
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 referencefor (auto elem : container) // capture by valuefor (auto& elem : container) // capture by (non-const) referencefor (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.