4

My understanding

  • In C++, an normal value can be converted to a pointer to itself using &value, or to a reference to itself through implicit cast.

  • As opposed to Rust, references aren't actual first-class types but merely a binary qualifier that some type declaration sites accept. References can be used transparently as if they were their referee, hiding away the internal pointer1. As such, we can use &refToValue to obtain a pointer to the referee immediately.

Question

If I convert a pointer, to a reference, back to a pointer, am I guaranteed to get the original pointer back? This would be the case in Rust, but again, Rust has first-class references defined as "normal" types abstracting over a pointer.

// Sample code by @chi
T x;
T* ptr1 = &x;
T& ref = *ptr1;
T* ptr2 = &ref;
assert(ptr1 == ptr2); // Is that always true according to the spec?

1: I know that the C++ standard doesn't require references to be implemented as pointers, but I cannot see how they could be implemented in any other way (not considering context-specific optimizations), especially if the answer to my final question is "yes".

YSC
  • 38,212
  • 9
  • 96
  • 149
MrAnima
  • 555
  • 1
  • 4
  • 13
  • 1
    Perhaps you should include some pseudocode to clarify. I guess you want something like `T x; T* ptr1 = &x; T& ref = *ptr1; T* ptr2= &ref; assert(ptr1 == ptr2);`. (Also note that in C++ no expression can have a reference type, which might be confusing at first. A reference can still be bound to an lvalue.) – chi Jul 17 '23 at 09:17
  • 3
    You might want to tag this question [tag:language-lawyer]. – YSC Jul 17 '23 at 09:17
  • 2
    https://stackoverflow.com/a/9263709/4165552 - this answer seems to be OK? – pptaszni Jul 17 '23 at 09:19
  • @pptaszni that's exactly the answer I'm looking for – MrAnima Jul 17 '23 at 09:21
  • Assuming no evil user-provided `operator&` :-} (as evil `deref` implementation in Rust). – Jarod42 Jul 17 '23 at 10:59
  • I find it more helpful to think of references as an **alias**, rather than a "hiding away the internal pointer" (because if there is an internal pointer, it's an implementation detail, and not programmatically exposed; but there doesn't need to be an internal pointer, and the optimizer can very well just (literally) alias the actual object). – Eljay Jul 17 '23 at 11:18
  • @Eljay I do understand this idea of alias now, I'm just a beginner in C++ and couldn't wrap my head around such a high-level concept in what I merely saw as a C successor. And sure the optimizer _can, in some situations_, remove the reference completely. What I mean is that — in the **general case** — you **have to** represent references as pointers, or you'd be losing information that could be required (like when restoring a real pointer from a ref). That's especially true across ABI/linker boundary. – MrAnima Jul 17 '23 at 11:30
  • *What I mean is that — in the **general case** — you **have to** represent references as pointers...* That part is incorrect. It may be true for a specific compiler implementation. It may be entirely false for another compiler implementation. – Eljay Jul 17 '23 at 12:14
  • Aside: There is no such thing as implicit cast. A cast is an explicit type conversion, by definition. – n. m. could be an AI Jul 17 '23 at 13:39
  • @n.m.willseey'allonReddit right, I should have said coercion, thanks for pointing out – MrAnima Jul 18 '23 at 08:25

2 Answers2

7

If I convert a pointer, to a reference, back to a pointer, am I guaranteed to get the original pointer back?

T x;
T* ptr1 = &x;
T& ref = *ptr1;
T* ptr2 = &ref;

Yes.


Full explanation on the initialisations

  • Per [expr.unary.op]/3.2, T* ptr1 = &x initializes ptr1 as a pointer to T pointing to the designated object (x).

The operand of the unary & operator shall be an lvalue of some type T. The result is a prvalue.
...
(3.2) Otherwise, the result has type “pointer to T” and points to the designated object ([intro.memory]) or function ([basic.compound]). If the operand names an explicit object member function ([dcl.fct]), the operand shall be a qualified-id.

  • Per [expr.unary.op]/1 and [dcl.ref]/5, T& ref = *ptr1 initializes ref to refer to a valid object, namely x as *ptr1 performs indirection on ptr1 and yields an lvalue denoting the object to which ptr1 points, namely x.

[expr.unary.op]/1 The unary * operator performs indirection. Its operand shall be a prvalue of type “pointer to T”, where T is an object or function type. The operator yields an lvalue of type T denoting the object or function to which the operand points.

[dcl.ref]/5 A reference shall be initialized to refer to a valid object or function.

  • Finally, per [expr.unary.op]/3.2 again, T* ptr2 = &ref initializes ptr2 as a pointer to T pointing to the designated object (x).

What about the pointer comparison?

A value of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory ([intro.memory]) occupied by the object35 or the first byte in memory after the end of the storage occupied by the object, respectively.

and [expr.eq]/3.2:

[refering to the result of ==, as per defined in /1]
if the pointers are both null, both point to the same function, or both represent the same address, they compare equal.

Meaning, regardless of the actual value of ptr1 and ptr2:

ptr1 == ptr2

yields true.

YSC
  • 38,212
  • 9
  • 96
  • 149
3

If I convert a pointer, to a reference, back to a pointer, am I guaranteed to get the original pointer back?

Yes.


references aren't actual first-class types ... binary qualifier that some type declaration sites accept

Indeed, while references are types, they can't serve as expression types.

So e.g. when you do &*foo, *foo isn't a reference.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Perfect answer! I appreciate the confirmation of my current understandings about the weirdness of reference types. – MrAnima Jul 17 '23 at 09:28