Unfortunately, many people confuse logical and physical meaning of C++ references.
Logical level
There is a crucial thing I found for myself about C++ references:
As soon as I have initialized a reference, it becomes unpronounceable. And what I mean by "unpronounceable" is the fact that always when you name a reference in runtime executable code - you automatically get the variable it refers to, so there is no way you can touch the reference itself. Reference is just an alternative name (an alias) for a variable.
So if you have something like int i_var = 10; int& i_ref = i;
no matter what executable expression you form with it, any mentioning of i_ref
would actually mean i_var
.
Also, some people find helpful to think of a reference as a "self-dereferencing pointer". So imagine you have a pointer int* p
, but each and every time you refer to it as p
you actually mean *p
. For instance, p = 10
would mean *p = 10
and &p
would mean &(*p)
- the address of int
which p
is pointing too. That is how logically references work.
It applies to your code too. As soon as you have
Point &p = plist[2];
(occured when you called func(plist[2])
) then p
and plist[2]
start reference to the same thing - some Point
object stored by the index of 2 in plist
. So now &plist[2]
and &p
are absolutely equal.
Type system level
If you noticed, I used terms "runtime executable code" or "executable expression". Let me clarify.
The compiler actually knows the difference between a
and b
:
int a = 0;
int& b = a;
std::cout << std::boolalpha
<< std::is_same_v<decltype(a), decltype(b)>; // -> false
As you see a
and b
types are different. However, std::is_same_v<decltype(a), decltype(b)>
gets evaluated at compile time so i did not consider it as an "executable expression".
Physical level
Notice, that until now I did not said that the address of a reference and the address of variable being referenced are the same. Why? Because if you think logically - they are not.
References have to be implemented in some way, whether you like it or not. I believe, in the example with i_var
and i_ref
compiler will simply replace all i_ref
with i_var
and any physical representation of "reference" will never exist. On the other hand, if you store reference inside a class it is likely to be implemented with a pointer.
Although, an implementation is compiler dependent, if reference actually is a pointer under the hood, it is obvious that the address of this pointer and the address of the object it is pointing to are different.
However, why should you care? You will never know the address of reference! In any executable expression, when you say i_ref
you imply i_var
, remember?:)
OK, if you are really-really curious "what is the address of a reference", there is a one case when you can figure it out - when reference is a member of a class:
int main()
{
int var = 10;
int& real_ref = var;
struct { int& ref; } fake_ref = { var };
std::cout << &var << std::endl; // address of var
std::cout << &real_ref << std::endl; // still address of var
std::cout << &fake_ref << std::endl; // address of reference to var
std::cout << sizeof var << std::endl; // size of var
std::cout << sizeof real_ref << std::endl; // still size of var
std::cout << sizeof fake_ref << std::endl; // size of reference to var
return 0;
}
Output on x64 compiler:
000000A9272FFBA4 <- same
000000A9272FFBA4 <- same
000000A9272FFBC0 <- different
4 <- same
4 <- same
8 <- different (8 on 64 bit and 4 on 32 bit compiler)