Shih-Chan Huang
Shih-Chan Huang

Reputation: 209

What does the '&' sign do when using 'auto' for iteration

recently I've encountered very very peculiar question when using auto in C++, just ... just look at the following code snippet :

my main function:

#include <list>
#include <iostream>
#include <stdio.h>
int main(){ 
    int a = 10, b = 20, c = 30;
    list<int> what;
    what.push_back(a);
    what.push_back(b);
    what.push_back(c);

    read(what);

    return 0;
}

And here's function read:

void read(const list<int>& con){

    for (auto it : con){
        printf("%p\n", &it);
        cout << it << endl;
    }
    return ;
}

And here's is the output :

0x7fffefff66a4
10
0x7fffefff66a4
20
0x7fffefff66a4
30

What the heck is that? Same address with different content !?

And more strange this is, if I modify the for-loop by adding an '&'
that is:

for (auto& it : con){

All the output makes sense immediately, the addresses would change by iteration

So my question is,
Why does the '&' sign make a change under this circumstance?

Upvotes: 3

Views: 1324

Answers (3)

pqnet
pqnet

Reputation: 6588

let's see the expanded version of the : loop syntax first.

for( auto it: container) {
    ...
}

is conceptually the same as

for( auto _it = container.begin(); _it != container.end(); it++) {
    auto it = *_it;
    ...
}

while the reference form:

for( auto& it: container)

is the same as

for( auto _it = container.begin(); _it != container.end(); it++) {
    auto &it = *_it;
    ...
}

So in the first case it is a copy of the items in the container, in the second case it is a (lvalue) reference of it, hence if you modify it in the second loop it affects the items in the container

The address issue too can be explained this way: in the copy example the local variable has always the same address in each loop iteration (because their lifetime do not overlap, the compiler has no reason not to use the same address in the stack), thought if you factorize the code inside a function you may observe it changing in different function invocation (because the stack size might be different), in the reference example the address is different every time, because taking the address of a reference will yield the address of the object being referenced (in this case, the item in the container)

Upvotes: 7

eerorika
eerorika

Reputation: 238351

for (auto it : con){

Same address with different content !?

This is very typical for variables with automatic storage duration. This has nothing to do with auto in C++. You would get the same result if you had used int:

for (int it : con){

The lifetime of it (as well as each automatic variable within the loop) is just a single iteration. Since the lifetime of the it in last iteration was ended, the next iteration can re-use the same memory, and that's why the address is the same.


Why does the '&' sign make a change under this circumstance?

Because T& declares a reference to type T. Reference variables are different from non-references (object variables). Instead of holding a value such as an object would, a reference instead "refers" to another object.

When you use the addressof operator on a reference, the result will be the address of the referred object; not the address of the reference (which might not even have an address, since it's not an object). That is why the address changes in the latter case. In this case, the references would refer to the int objects that are stored in the nodes of what (because con itself is a reference, and refers to the passed object).


† I mention in C++, because in C auto is in fact a storage class modifier that signifies automatic storage class. It has never had that meaning in standard C++, and its use obsolete in C as well. It's a vestigial keyword from the B language.

In C++, auto declares a type that will be deduced from context.

Upvotes: 20

Bathsheba
Bathsheba

Reputation: 234715

Note that auto is standing in for int in your case. So it's a red herring. Consider

for (int i = 0; i < 10; ++i){
    int j = i;
    cout << (void*)&j << '\n';
}

Since j has automatic storage duration, it is most likely created each time with the same address - but points to a different value - , j is being pushed then popped from a stack on each iteration (let's set aside compiler optimisations). That is what is happening in your case with for (auto it : con){. it has automatic storage duration.

When you write

for (auto& it : con){

it is a reference to an int within the container con, so its address will differ on each iteration.

Upvotes: 6

Related Questions