To answer the first question, we can start with the second, which will make the motives of the first more obvious.
The build (called range-based for , or is range-based ):
for (declaração : expressão) corpo
It is only syntactic sugar and is transformed into the following construct:
{
auto && __range = expressão ;
auto __begin = begin_expr; // normalmente begin_expr é begin(__range)
auto __end = end_expr; // normalmente end_expr é end(__range)
for ( ; __begin != __end; ++__begin) {
declaração = *__begin;
corpo
}
}
Note that this transformation conforms to c ++ 17 .
What happens is that this type of for
works with any expression that can be iterated. In its example, std::vector
can be iterated, since it has the member functions std::vector::begin
and std::vector::end
, which return iterators to the beginning of the vector and to the end, respectively. For example:
std::vector<int> v{1, 2, 3};
int soma = 0;
for (int i : v) soma += i; // Soma todos os elementos de 'v' em 'soma'.
assert(6 == soma);
If we take only the part of the loop from the previous example, we see that
-
int i
is the statement ,
-
v
is the expression and
-
soma += i;
is the body .
In the transformation (a little more simplified), we will have:
{
auto __begin = begin(v);
auto __end = end(v);
for ( ; __begin != __end; ++__begin) {
int i = *__begin;
soma += i;
}
}
Notice that the int i = *__begin
is where each element of the range (the v
in this case) is assigned to the i
variable. The body of the loop comes next.
is interval-based , as said before, works with any range. Therefore, any container that is able to achieve a start and end iterator will work with this construct (eg fixed-length arrays, some standard library containers such as std::map
, std::vector
, std::array
etc.).
If the iterator concept is unfamiliar, see it as an object that can traverse the elements of a container. For example, a pointer to the first vector element can be considered an iterator, because when we increment the pointer, we get access to the elements in sequence from that vector, until it reaches the end, which is a pointer to after the last element (meaning the arrival, or end of the iteration). In this case, __begin
and __end
would be such iterators, where __begin
has access to the first element (and is incremented during the loop, to access the following elements) and __end
represents the end of the iteration. When __begin
is equal to __end
, it means that the loop has ended and all elements have been iterated.
Returning to the first question, we see that it is now obvious why we declare vi
as a reference. If we look at your example:
for (auto &vi : v) {
vi *= fator;
}
We have auto &vi
as declaration , v
as expression and vi *= fator
as corpo
:
{
auto __begin = begin(v);
auto __end = end(v);
for ( ; __begin != __end; ++__begin) {
auto &vi = *__begin;
vi *= fator;
}
}
When we assign the elements of the v
vector to each iteration, we get the reference for that element. If we had the statement as only auto vi
(or double vi
), we would be copying the element to the vi
variable. Since the intention is to modify the element, a copy would not work, we want write access to the element, so the reference is used.
Warning , the use of __
as a prefix of names is reserved by standardization. Do not write names like this on their own because they are used exactly to prevent user code from conflicting with standard library code, syntactic sugars, or compiler intrinsic functions.