4

I was looking at the C++ standard regarding member reference operators (the unary * dereferencing operator, the -> member accessor operator) as well as many other related questions:

C++ - Difference between (*). and ->?

ptr->hello(); /* VERSUS */ (*ptr).hello();

C++ pointers difference between * and ->

I saw that most answers stated that p->m is syntactic sugar for (*p).m as defined by the C++ Standard (5.2.5, paragraph 2):

The expression E1->E2 is converted to the equivalent form (*(E1)).E2

Many comments also noted that because operator* and operator-> are overloadable in classes, they should be overloaded uniformly to ensure consistent behavior.

These statements seem to contradict each other: if (as per the standard) E1->E2 is converted to the equivalent form (*(E1)).E2, then what would be the purpose of overloading operator-> (as is permitted by the standard)?

Simpler stated, are these two parts of the standard in conflict, or am I misunderstanding the Standard?
Does the E1->E2 equivalence transformation to (*(E1)).E2 apply to all complete types or only to built in ones?

Community
  • 1
  • 1
callyalater
  • 3,102
  • 8
  • 20
  • 27
  • 2
    See point 3 of clause 5, "Clause 5 defines the effects of operators when applied to types for which they have not been overloaded." – M.M Mar 02 '16 at 22:09

1 Answers1

7

The conversion from E1 -> E2 to (*(E1)).E2 only applies to raw pointer types. For class types, E1 -> E2 evaluates to (E1).operator->().E2, which potentially might recursively expand out even more copies of operator-> if the return type of operator-> is not itself a pointer type. You can see this by creating a type that supports operator* but not operator-> and trying to use the arrow operator on it; you'll get an error that operator-> is undefined.

As a follow-up, it's common to implement operator -> in terms of operator * in a way that makes the semantics of -> match the semantics for pointers. You often see things like this:

PointerType ClassType::operator-> () const {
    return &**this;
}

This expression is interpreted as

&(*(*this)),

meaning "take this object (*this), dereference it (*(*this)), and get the address of what you find (&(*(*this)).)." Now, if you use the rule that E1 -> E2 should be equivalent to (*(E1)).E2, you can see that you end up getting something equivalent.

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • This is a great explanation! I never realized the resolution to a raw pointer before the equivalent expression conversion. The example was a very helpful illustration. – callyalater Mar 03 '16 at 15:21