Operator-> in C++

I recently discovered an interesting fact about how operator->() behaves differently from other operator overloads in C++.

Consider this straightforward example:

#include <iostream>

struct A {
    void f() { std::cout << "A::f\n"; }
};

int main() {
  A* pA = new A;
  pA->f();
  delete pA;
  return 0;
}

This prints “A::f” — no surprises. We create a pointer to an A and use -> to dereference and call f().

The surprise comes with this:

#include <iostream>

struct A {
  void f() { std::cout << "A::f\n"; }
};

struct B {
  A* operator->() { return &a; }
  A a;
};

struct C {
  B operator->() { return b; }
  B b;
};

int main() {
  C c;
  c->f();
  return 0;
}

What does c->f() do? The neat thing about operator-> is that it continually reapplies itself until it reaches a raw pointer. When the raw pointer is reached, it’s dereferenced and f() is called.

So c->f() expands to (c.operator->())->f(), which gives b->f(), which expands again until we reach an A*. The output is still “A::f”.

The C++ standard (§13.5.6) describes this:

An expression x->m is interpreted as (x.operator->())->m for a class object x of type T if T::operator->() exists and if the operator is selected as the best match function by the overload resolution mechanism.

This behavior is what makes smart pointer wrappers like std::unique_ptr and std::shared_ptr transparent — calling ptr->method() just works, no matter how many layers of wrapping are involved.