What optimization is at play here that gives the same address? I believe a temporary object should materialize since we cannot bind low-level const
pointer to low-level non-const
.
There is no trick:
- it is valid to convert an
int*
to const int*
, and
- it is valid to convert an
int**
to a const int * const *
, however
- it is not valid to convert
int**
to const int**
(but that case isn't relevant here).
See Why isn't it legal to convert "pointer to pointer to non-const" to a "pointer to pointer to const".
Current wording
You only have a single i_p
pointer, and naturally, both &i_p
in main
and &i_p
in foo
should yield the same address.
foo
accepts a reference to a pointer, so it should refer to that in main
.
const int * const
is reference-compatible with int *
because a a pointer to int*
, i.e. int**
could be converted to const int * const*
via a qualification conversion.
Due to this, a reference to const int * const
can bind to int*
.
There is nothing which would necessitate the creation of a second object, at least not in the current draft.
const int&
can bind to int
without creating a separate object, and the same principle applies to your case.
Historical defects explaining your observed behavior
However, it didn't always use to be like that, and qualification conversions haven't always been considered during reference binding.
CWG2018. Qualification conversion vs reference binding points out that qualification conversions aren't considered and temporary objects are created, such as in this example:
const int &r1 = make<int&>(); // ok, binds directly
const int *const &r2 = make<int*&>(); // weird, binds to a temporary
const int *&r3 = make<int*&>(); // error
const int &&x1 = make<int&&>(); // ok, binds directly
const int *const &&x2 = make<int*&&>(); // weird, binds to a temporary
const int *&&x3 = make<int*&&>(); // weird, binds to a temporary
Note: there is still some compiler divergence; GCC considers x3
to be ill-formed, and clang performs temporary materialization.
The example r2
is exactly your case.
If the const int* const &i_p
isn't simply binding to the i_p
in main
but creates a brand new temporary pointer when foo
is called, then it would be a separate object. Note that const&
can bind to temporary objects thanks to temporary materialization.
If there is a separate temporary object which i_p
in foo
binds to, then it would also need to have a separate address from that in main
.
This behavior is a defect though, fixed by CWG2352: Similar types and reference binding.
The new wording was implemented in GCC 7 and clang 10, which is why you no longer see two separate pointers being created.
foo()
cannot actually modify its parameter,clang
is smart enough to hijackmain()
's stack frame and recycle it infoo()
? All references themselves are, by definitionconst
, for all practical matters. It should be interesting if the same optimization works for a non-reference, but aconst
parameter. – Consideri_p
anytime, resulting in this optimization being able to observe it, infoo()
. Fail. – Consider[[no_unique_address]]
has been added in C++20 so associated optimization code might be used more widely? – Romitoi_p
inmain
ends only after the function call. Soi_p
inmain
and a temporary object bound to the function parameter (if there was one) are not allowed to have the same address, because their lifetimes overlap. – Dubenko