Hi Arthur,

Thank you for your response.

The answers to problems #1 and #2 are related to the important detail. operator-> will keep going until it resolves to a native pointer type, then binds whatever is on its RHS to something in the scope of that pointer's type. This mechanism would not remove anything from this, but would change the final binding to a function call within the current class scope instead. So the purpose of calling operator->() is to resolve it to the pointer type that can then be used in the subsequent lookup. If Pointee::*f has multiple overloads, it uses whichever one the compiler would have otherwise bound the name to in the default behavior.

Major problem #3: Yeah, I forgot to include the args in the function signature when typing it out. It would have to be something like:
    template<typename R, typename... Args>
    T operator->(Pointee* p, R (Pointee::*f)(Args...), Args... args) {
      return (p->*f)(std::forward<Args>(args)...);
    }

Major problem #4: The process here is that the compiler resolves the chained operator-> calls to a native pointer of type Ptr as before, looks for the Ptr:: scope binding of whatever's on the RHS, then looks for an overload using that binding in the current class scope. If it finds the overload in the current class scope, it calls that, otherwise it binds to whatever it found in Ptr:: scope as per the default. While there would be nothing to prevent you from allowing this mechanism to similarly overload member access to return a modified data member reference, it seems less useful. The point of this is mostly to allow the container class to perform transformations on the function call.

Minor problem #5: I don't think arrow proxy solves what I want to do (but I'll look at it harder). As currently specified, operator-> has to eventually resolve to a pointer type, then accesses a member or member function of that type. I don't see a way within the current specification of, e.g., modifying the return type of the function that it resolves to.

You certainly don't want `x->y()` to behave subtly differently from `(*x).y()`. So any solution you come up with must work uniformly for both `operator->` and `operator*`.
This is likely the biggest problem, which may well be unsolvable with the inability to do anything to change the member access operator.


On Tue, Jun 15, 2021 at 9:35 AM Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:
On Tue, Jun 15, 2021 at 12:19 PM Steve Thomas via Std-Proposals <std-proposals@lists.isocpp.org> wrote:

At the moment, an overloaded operator->() eventually resolves to a pointer of arbitrary type, before calling some method or accessing a data member from the type of that pointer.

  class SmartPointer {
   public:
    Pointee* operator->();
  };  
  SmartPointer s;
  s->f();  // Resolves to a Pointee* p, then calls Pointee::*f(p).

Important detail: If the return type of `SmartPointer::operator->()` is not a native pointer type, then the compiler will generate a call to that type's operator->(), and so on forever or until a native pointer type is finally reached.

I would like to change this, so that instead of immediately calling Pointee::*f(p), we look for a method in the current class scope and if found, call that instead:
  class SmartPointer {
   public:
    Pointee* operator->();

    template<typename R, typename... Args>
    T operator->(Pointee* p, R (Pointee::*f)(Args...));
  };
  SmartPointer s;
  s->f();  // Resolves to s.operator-><Pointee>(s->operator(), &Pointee::f);

Minor problem #1: What's the purpose of calling the zero-argument `operator->()` here? Shouldn't this example be more like
    struct SP {
        T *ptr_;
        template<class R, class... As>
        R operator->(R (T::*pm)(As...));
    };
?

Major problem #2: What if `Pointee::f` has multiple overloads?

Major major problem #3: Show me the implementation of `operator->` above.
    struct SP {
        T *ptr_;
        template<class R, class... As>
        R operator->(R (T::*pm)(As...)) {
            return (ptr_->*pm)(args...);  // wait, where the heck did these `args...` appear from? How did we get them?
        }
    };
 
Major problem #4: What if `Pointee::f` is a data member, or pseudo-destructor, or any of the other kinds of things that are allowed to appear on the right-hand side of an `->` operator?

Minor problem #5: Couldn't you achieve your stated goal more easily by just making your optional's `operator->` return a proxy object?
Are you familiar with the "arrow proxy" idiom?
https://quuxplusone.github.io/blog/2019/02/06/arrow-proxy/
I think you need some kind of proxy no matter what. You certainly don't want `x->y()` to behave subtly differently from `(*x).y()`. So any solution you come up with must work uniformly for both `operator->` and `operator*`.

I suggest you read about arrow_proxy, then see if it completely solves your problem, and if not, then come back with a worked example — a complete program, with a unit test that "fails now, but would pass if my fantasy feature existed." Show the code you'd write using your fantasy feature that would make the test pass. Then, people can either show different ways to make it pass within today's language, or perhaps they might say "oh yeah, I see how that would be useful."

HTH,
–Arthur