Well, what I want ( and probably many of us want ) is a member function that receives a member pointer as a parameter with a simple form of calling ( like -> or . ), in a way that you have "this" and "member_ptr" in same code;

Said that, and since I thought this thru binary operator.() here goes my 2cents : 

struct X {
    template< typename MemberType > 
    auto operator .( MemberType m ) {  // m may be a member function or member data
        // here we have access to "this" and "m";
    };

    int& operator .( int X::*field ) {  // an overload to use for  X::b or X::c
         return this->*field;   // does not fall into operator.() case
    };

    auto operator.() default;

    template<typename T> int a(T t) { return t; };
    int b;
    int c;
    float d;
} x;

Upon a call to 

x.a(10);   

1 - The compiler will try to solve x.a by current template/overload rules and will eventually decide to call "int X::a(int)", ( may need to instantiate or not, you may have int a(int) overloaded inside X )
2 - After that it should check for overloaded operator.(), template and non template versions and thru "normal" instantiaton rules (SFINAE/most vexing). And decides to instantiate operator.( int (X::*) (int) ) then, call x.operator.( &X::a ). This way it will find the proper instantiation even among other operator.() and a´s overloads.
2.1 - If you declare any overloaded operator.() and the compiler does not found any possibility, it will try to fall back" to operator.() default" if it is declared;
2.1.1 - If there is no default operator. declared, it should throw a compilation error.
3 - When you don´d provide any overloads, the compiler will create an implicit default with "current behavior".

2.1, 2.1.1 and 3 are analogous to constructor/default rules.
And yes, I´m aware that item 2 may mess up a "little" with current template instantiation rules :) 

So:

1 - x.a(10);   become:   ( x.operator.( &X::a ) ) (10);       // instantiated int X::a(int) and operator.( int (X::*)(int) ),*NOT* passing 10 to operator.(), just &X::a
2 - x.b = 10;  become :    x.operator.( &X::b )  = 10;        // call overloaded operator.( int X::* ) returning an int&, then call built in operator=(int&, int)
3 - x.d = 1.9; uses default


If you look closer, you will always calls operator.() with a value that is a constexpr, so technically, it may be implemented as a non-type template parameter unary operator.()

template<int X::* mem> auto& operator.() { return this->*mem; }
x.b become x.operator.<&X::b>() // I´m not completelly sure where the <> should be here

However, another more closer++ look, you may see that if operator.() is inherited ( as it should ), one can get the derived class from the type parameters.
But since funciton deductiong rules are different then class deduction rules, I´m not so sure that can be achived thru a unary form, so a binary operator.() should get those cases:

struct X{
   template<typename T> auto& operator.( int T::*mem ) { return ((T*)this)->*mem; }  // T must be a X or derived from X
   ...
};
struct Y : X {
   int h; 
};

Y y;
y.h become x.operator.(&Y::h);   // the call is binary now

Finally, properties:

struct X {
     template<typename T> struct property { ... };
     template<typename T, typename MemT> auto& operator.(property<MemT> T::* mem ) { set_value and invalidate widget }   // yay properties !  
};
struct Z : X{
     property<int> top;
};

x.top  = 10   will convert to x.operator.( &Z::top ) = 10

The operator -> may work in a similar form.  


BR
Cleiton

Em ter., 15 de jun. de 2021 às 16:29, Steve Thomas via Std-Proposals <std-proposals@lists.isocpp.org> escreveu:
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?
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
--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals