2

Given that the * operator returns a reference to the object, and since -> is basically a shorthand for (*a).b, why does C++ define a separate overload? To me it seems like you would want to keep * and -> in sync at all costs to avoid weird bugs, but the * operator already works to define -> as well.

EDIT: for clarification, my reasoning is that if the implementation of -> is just (*a).b. if we just overloaded the * operator and treated -> basically like a macro to the (*a).b form, then the overloaded * would apply to the new expression and we would get an overloaded -> for free

k huang
  • 409
  • 3
  • 10
  • 3
    What if you want to use `->` for something else other than pointers? The same thing with `*`. – PaulMcKenzie Aug 30 '22 at 18:59
  • 1
    Even if you restrict yourself to only considering pointers, they do different things. In the case of `a->b`, you needed a `*` and a `.` to get `(*a).b`. – user4581301 Aug 30 '22 at 18:59
  • Yeah - like @Paul says. Think of the way in which the `<<` and `>>` operators are overloaded for streams. – Adrian Mole Aug 30 '22 at 19:00
  • Also, they may require separate implementations. – Galik Aug 30 '22 at 19:01
  • @PaulMcKenzie what do you mean? – k huang Aug 30 '22 at 19:02
  • @khuang -- [See Boost::Spirit](https://www.boost.org/doc/libs/master/libs/spirit/doc/x3/html/spirit_x3/introduction.html). Look what the `*` is used for. – PaulMcKenzie Aug 30 '22 at 19:02
  • 1
    Semi-related fun: [What is the "-->" operator in C++?](https://stackoverflow.com/q/1642028/4581301) – user4581301 Aug 30 '22 at 19:02
  • 1
    @user4581301 my point is, if the implementation of -> is just (*a).b. if we just overloaded the * operator and treated -> basically like a macro to the (*a).b form, then the overloaded * would apply to the new expression and we would get an overloaded -> for free. – k huang Aug 30 '22 at 19:07
  • 1
    I don't see why it couldn't work. But it goes in line with C++ not automating operator overloading (needing to overload six comparison operators separately pre-C++20, etc). – HolyBlackCat Aug 30 '22 at 19:09
  • 2
    @khuang -- What if I want to develop a C++ graph library, where `->` means `a->b` are two nodes connected to each other? It has absolutely nothing to do with pointers, but I have the freedom to do this (and I wouldn't be surprised if it hasn't already been done). Why would I want `*` to come along for the ride, when I didn't pay for it? – PaulMcKenzie Aug 30 '22 at 19:10
  • 3
    @PaulMcKenzie *"I have the freedom to do this"* But do you? `operator->` has to either return a pointer (or a class that overloads `->` in the same way), and the rhs must be a member variable name (meaning, no custom graph node names?). Inside of `operator->` you have no way to detect which member variable was referenced this way. – HolyBlackCat Aug 30 '22 at 19:12
  • Well, that may be true. My main point is that C++ isn't going to auto-generate overloaded operators, unless you opt-in to do it. I guess the case of overloading `*` to do non-pointer things should have been used. – PaulMcKenzie Aug 30 '22 at 19:17
  • 1
    I can see a case for defaulting `->` to be implemented with `*` and `.`, but I'd leave the option for doing something sneakier open. – user4581301 Aug 30 '22 at 19:17
  • 1
    @PaulMcKenzie C++20 rewritten comparisons are [not opt-in](https://stackoverflow.com/q/65648897). :( – HolyBlackCat Aug 30 '22 at 19:19
  • 1
    `operator->` does something more than `operator*` since it returns the first pointer returned even if there's a long chain of `operator->`s. [Example](https://godbolt.org/z/YsPPrq96h) – Ted Lyngmo Aug 30 '22 at 19:21
  • @HolyBlackCat -- Well, I haven't used C++20 a great extent, but from that link, not too many are happy about it. – PaulMcKenzie Aug 30 '22 at 19:21

2 Answers2

0

Originally, C++ has not put any rules on which operators you define, or whether you define them in a logically symmetric manner. You can choose to implement just 1 comparison operator , or many, though later standards have tried to regularise this somewhat.

A couple of ways you could take advantage of this:

  1. You may want to stop * from being used to get your internal object, and perhaps take its address.

Yes, -> gets your internal object, but it doesn't leave it in an accessible state, a member must be called.

Effectively, -> provides better OOP than *.

Ok, yes, as HBC points out, the object caller can call the method operator->() directly and get the object, but I would like to think that this would be an obvious code smell in any code review. I mean, you could just reinterpret_cast<> the object bytes into the pointer, if we go down that path.

Of course, yes, if you are going to implement *, then you might as well implement ->, but not necessarily vice-versa.

  1. The other thing you might do is implement * to give you a raw blob of data or simple struct, that you might then transmit or store, but -> gives you a manipulator object paired with the data, so adding some object richness to a simple struct. This would be in a case where you can't simply add the richness directly, perhaps because the data was provided by an older C library.

You could even have the simple blob implement some compact c-style nested struct polymorphism, while -> gives you an equivalent smart manipulator.

In this way -> has provided a "view" on your data. Potentially that might be confusing, or it might be the obvious way to provide object capabilities to a piece of dumb data, that perhaps still needs to also be accessed as dumb data. All these tools are provided to API definers to use wisely.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • You can call `.operator->()` with functional notation to get yourself a pointer. In fact, C++20 [`std::iterator_traits`](https://en.cppreference.com/w/cpp/iterator/iterator_traits) does this to deduce `::pointer` for iterators that don't define it themselves. – HolyBlackCat Aug 30 '22 at 19:14
  • @HolyBlackCat Fair point, OK, yes, you /can/ but the code that you wrote is never going to get through a peer review, while the use of `&*` just might :-) – Gem Taylor Aug 30 '22 at 19:16
  • sorry, I'm not really sure what you're trying to say here @Gem, could you elaborate? are you saying that you would want `*` have a completely different purpose from `->`? Wouldn't that be really confusing to use? – k huang Aug 30 '22 at 19:17
  • @khuang different classes introduce different semantic to operators. Of course it might not consistent, but consider cases where the operator "native" semantic just doesn't make any sense. E.g. `/` for filesystem classes means path concatenation, and it's C++ standard library – The Dreams Wind Aug 30 '22 at 19:34
  • @khuang You can use overloaded operators for domain-specific purposes. As stated previously, the `/` to mean a path separator, not division. It is also used in Howard Hinnant's date library, to separate month, day, year, etc. And in the main comments, I brought up the `*` used in the Boost Spirit parser to mean Kleene closure. None of those things have anything to do with mathematics (the `/` operator) or pointers (the `*` operator). You can also add in the pipe symbol (`|`) to mean separation, not bitwise OR. – PaulMcKenzie Aug 30 '22 at 20:24
  • The classic example of this is the use of << and >> in streams which has nothing to do with bit shifting. I don't defend these as great ideas. Very often they /are/ gimmicks and they will eventually cause the author much pain. – Gem Taylor Aug 30 '22 at 20:31
0

The simple answer is - and you'll find it many times with C++ - why not?

You can see the same for other operators: operator+, operator- unary and binary; operator<, operator<=, operator==, operator>=, operator>, operator!=, and many others - these are deducible from each other if you use them in the standard meaning. Yet - it's possible to override them.

Now, reason for overriding might differ from project to project: sometimes, you'd like to implement an EDSL and unary * might have a radically different meaning than ->; sometimes you'd like to optimize the last bit out of the architecture; sometimes you're porting code that someone copy-pasted from e.g. Java and want to put special checks in ->, which are unneeded in *; your reasons might vary.

However, C++ doesn't enforce one specific 'good' way of doing this. In fact Bjarne himself said (at the C++ Budapest meetup), he'd like to keep C++ diverse and allow many programming styles, paradigms. So the language itself doesn't limit you.

lorro
  • 10,687
  • 23
  • 36