0

This question discusses how the arrow operator -> is automatically re-applied to the return value of an overloaded operator->() until the value returned is a raw pointer, at which point the raw pointer is dereferenced as if with ->. However, the same is not true when one has a pointer-to-pointer type that they wish to dereference to the base value - they must use (*ptr_to_ptr)->foo() instead. It seems to me that usage as ptr_to_ptr->foo() would be unambiguous, even more so than the automatic re-application of -> onto returned values until a raw pointer is returned. So, what was the reasoning behind this decision?

minimal working example:

#include <iostream>

struct Dog {
    void bark() { std::cout << "woof!" << std::endl; }
};

struct DogWalker {
    Dog* dog;
    Dog* operator->() {
        return dog;
    }
};

struct DogOwner {
    DogWalker walker = { new Dog() };
    DogWalker operator->() {
        return walker;
    }
};

void main()
{
    DogOwner owner;
    owner->bark(); // works, prints "woof"

    Dog** ptr_to_ptr = new Dog*;
    *ptr_to_ptr = new Dog;
    (**ptr_to_ptr).bark(); // works
    (*ptr_to_ptr)->bark(); // works
    //ptr_to_ptr->bark(); // ERROR
    //C2227: left of '->bark' must point to class/struct/union/generic type
}
Jamie S
  • 111
  • 1
  • 10
  • Because it doesn't - that's the way the language is defined. –  Apr 06 '18 at 22:46
  • Are you asking for standards committee discussion about the proposal and rejection of that language feature? What if there is none? `->` is much older than the `operator->()` you compare it to. – Drew Dormann Apr 06 '18 at 22:52
  • @DrewDormann I suppose that I would be. I tried to find some discussion about it and couldn't, so I asked here to see if anyone had any insight. There are numerous other questions on this site whose answers discuss why certain things work the way they do in c++, and I thought there might be an opportunity for similar discussion here. Your comment that `->` is much older than the `operator->()` is perhaps the starting point - would my question be better formulated as why `operator->()` has the automatic reapplication that it does? – Jamie S Apr 06 '18 at 23:01
  • @NeilButterworth Evidently. But is there some historical reason for the similar operations having different semantics? – Jamie S Apr 06 '18 at 23:02
  • The purpose of this is to allow for smart pointer classes, so they overload `->`. The repeated application allows them to return other smart pointers. But pointer-to-pointer is not the same thing. – Barmar Apr 06 '18 at 23:03
  • Too complicated? Not necessary? –  Apr 06 '18 at 23:09
  • fyi The lanuage specifies that `p->m;` is exactly equivalent to `(*p).m;` So we need to include should `operator *` also be applied recursively. – Richard Critten Apr 06 '18 at 23:09

1 Answers1

2

The language adopts much of its semantics from C. The -> operator when applied to a pointer type is only valid if the pointer points to a non-array composite type. Since C doesn't have classes, C++ defined its own semantics for the overloaded -> that made sense for the smart pointer use cases.

You can achieve the behavior you want with a helper class.

template <typename T>
struct Unwrap {
    T *p_;
    Unwrap (T *p = 0) : p_(p) {}
    T * operator -> () const { return p_; }
};

template <typename T>
struct Unwrap<T *> {
    T **p_;
    Unwrap (T **p = 0) : p_(p) {}
    Unwrap<T> operator -> () const { return *p_; }
};

template <typename T>
Unwrap<T> make_unwrap (T *p) { return p; }

You can then use it like this:

struct foo {
    void bar () { std::cout << "Hello\n"; }
};

int main () {
    foo p;
    auto pp = &p;
    auto ppp = &pp;
    auto pppp = make_unwrap(&ppp);

    pppp->bar();
}

Try it online!

jxh
  • 69,070
  • 8
  • 110
  • 193
  • So essentially, it's because `->` on pointers came from C (so couldn't/shouldn't be changed), but C++ introduced the overloaded `operator->()`. Good to know, thanks, and helpful example! – Jamie S Apr 07 '18 at 19:59