14

Wouldn't it make sense if p->m was just syntactic sugar for (*p).m? Essentially, every operator-> that I have ever written could have been implemented as follows:

Foo::Foo* operator->()
{
    return &**this;
}

Is there any case where I would want p->m to mean something else than (*p).m?

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 1
    It seems to follow naturally from the fact that `x->y` is interpreted as `(x.operator->())->y` for classes that there is this recursion. Maybe they thought it wouldn't harm to allow the recursion and thus didn't forbid it? At least i couldn't find some explanation of it in the ARM. – Johannes Schaub - litb May 30 '10 at 13:00

5 Answers5

17

operator->() has the bizarre distinction of implicitly being invoked repeatedly while the return type allows it. The clearest way to show this is with code:

struct X {
    int foo;
};

struct Y {
    X x;
    X* operator->() { return &x; }
};

struct Z {
    Y y;
    Y& operator->() { return y; }
};

Z z;
z->foo = 42;          // Works!  Calls both!

I recall an occasion when this behaviour was necessary to enable an object to behave as a proxy for another object in a smart-pointer-like context, though I can't remember the details. What I do remember is that I could only get the behaviour to work as I intended using the a->b syntax, by using this strange special case; I could not find a way to get (*a).b to work similarly.

Not sure that this answers your question; really I'm saying, "Good question, but it's even weirder than that!"

j_random_hacker
  • 50,331
  • 10
  • 105
  • 169
  • 1
    +1 But the language could still enforce this weird rule even if `operator->` was hardwired into the language, right? Technically it would be slightly more than syntactic sugar -- *syntactic syrup* maybe? – fredoverflow May 30 '10 at 12:59
  • 2
    Do you mean if the language spec asked the compiler to rewrite `a->b` to `(*a).b`, would it still make sense to recursively invoke `operator*()`? No, since it's sometimes necessary to stop before you "get to the end" (i.e. return a pointer/pointer-like object, e.g. so you can modify it, instead of returning the final pointed-to object). The `operator->()` recursion weirdness is possible (necessary?) because it's not really a "proper" 2-arg operator -- the 2nd "argument" must be a `struct` field name, which is something that can't be expressed in the C++ type system. – j_random_hacker May 30 '10 at 13:13
  • 3
    P.S: I would call it *syntactic molasses* -- slowing my understanding to a crawl... :P – j_random_hacker May 30 '10 at 13:14
3

One use case might be when you're creating a DSL inside C++, along the lines of Boost Karma (although it does not seem to overload -> in their library), where you completely change the meaning of the traditional C++ operators. Whether that's a good idea is certainly up to debate.

Mark Rushakoff
  • 249,864
  • 45
  • 407
  • 398
  • +1 for the reason itself, but that feels like a terrible idea to me... About as bad as writing a copy ctor that changes the state of the world (since sometimes the compiler can choose to call it or not). – j_random_hacker May 30 '10 at 12:54
1

Regular pointers offer p->m and (*p).m, with p->m being by far the more common. If you only allowed overloading one of them, then it would be inconsistent with the default type. As for why not let the compiler rewrite it, the simple answer is because sometimes, you don't want operator-> to return T*, where operator* returns T&. Allowing them to be overloaded separately allows combinations that you can't necessarily think of. But it would be silly to disallow it, just because we can't currently think of any reason to change it. Much like, you could have an int128 class and overload operator+= to mean exponentiation instead, if you so wanted.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • I agree with the idea that forbidding something just because you can't think of a good reason to allow it is bad, so +1, but then I think any argument supporting the overloading of `->` would also support the overloading of `.`, which C++ does not not allow you to overload. – j_random_hacker May 31 '10 at 05:19
  • Not necessarily. Remember that at least one operator must remain non-overloadable, so that people can still access the base class at all times. – Puppy May 31 '10 at 09:42
  • I don't follow sorry -- why is it important to be able to always access the base class? Furthermore, it's not clear to me why `->` is more suitable for overloading than `.`. – j_random_hacker May 31 '10 at 11:56
  • 1
    j_random_hacker: Let's take class x and let's say it overloads two and only two operators, the `.` operator and the `->` operator. The `.` operator returns class y and the `->` operator returns class z, and let's say class y overloads operator `->`. If we call `x->member`, then the compiler translates that to `x.operator -> ()->member`, but x has overloaded operator `.` which returns y, so which operator `->` are we calling? It's this ambiguity that prevents us from overloading operator `.`, Similar ambiguities can happen with other operators, but they can happen much easier with operator `.`. – Joe D Jul 26 '10 at 11:00
  • @Joe: Yeah, I think the problem is that neither `.` nor `->` is truly an "operator". All other operators take 1, 2 or 3 operands that have types that can be described in C++, while `.` and `->` both take one object operand and one operand that must be a "struct field name". At least one of these 2 "operators" must be unoverloadable for any member access to be possible, and I can see that `->` can be implemented in terms of `.` while the reverse is not true, so I can see why `.` was chosen as the unoverloadable one. – j_random_hacker Mar 16 '11 at 01:41
0

You might want to do some other operations, like incrementing a variable (access counting) or even some safety checks etc. On the other hand, i had never need to overload the operator-> ...

PeterK
  • 6,287
  • 5
  • 50
  • 86
-1

I think it's used for shared_ptr<> in boost (for example). That makes them "look" like normal pointers, although they're special pointers (reference counting afaik).

  • 7
    That doesn't tell me anything interesting I'm afraid. Why does `shared_ptr` need to overload `operator->()`, rather than simply overload `operator*()` and let the compiler rewrite `p->m` to `(*p).m`? This has the obvious advantage of consistency -- you could use either syntax and get the same results, which is surely what everyone expects. So why isn't it done this way? – j_random_hacker May 30 '10 at 12:40