Consider the following program:

// P is designed to be some proxy for T
template<class T>
struct P
{
T* operator ->();
};

// PP<T> is designed to be some proxy for the proxy P<T>
template<class T>
struct PP
{
P<T> operator ->();
};

// Any old class
class C
{
int f();
};

int main(int, char**)
{
PP<C> c;
return c->f();
}

This compiles successfully on MSVC6, MSVC8 (beta 2) and Comeau Online.

How is C::f() found? (it is indeed being found, as can be confirmed by
attempting to link it.) By what rule does "c->f()" get interpreted as
"c.operator->().operator->().f()"? Usually one cannot do more than one
"implicit conversion" in one go. Is there a special rule, or indeed a
general rule that I am missing here?

One can seemingly extend the example to further layers of indirection, e.g.

template<class T>
struct PPP
{
PP<T> operator ->();
};

template<class T>
struct PPPP
{
PPP<T> operator ->();
};

then change PP<C> in main to be PPPP<C>.

Re: "Double dispatching" of operator-> by Igor

Igor
Tue Feb 07 07:11:38 CST 2006

"Simon Trew" <noneofyour@business.guv> wrote in message
news:eZ2HPT%23KGHA.1192@TK2MSFTNGP11.phx.gbl
> Consider the following program:
>
> // P is designed to be some proxy for T
> template<class T>
> struct P
> {
> T* operator ->();
> };
>
> // PP<T> is designed to be some proxy for the proxy P<T>
> template<class T>
> struct PP
> {
> P<T> operator ->();
> };
>
> // Any old class
> class C
> {
> int f();
> };
>
> int main(int, char**)
> {
> PP<C> c;
> return c->f();
> }
>
> This compiles successfully on MSVC6, MSVC8 (beta 2) and Comeau Online.
>
> How is C::f() found? (it is indeed being found, as can be confirmed by
> attempting to link it.) By what rule does "c->f()" get interpreted as
> "c.operator->().operator->().f()"?

That would be c.operator->().operator->()->f()

An expression a->b where a is not a pointer is interpreted as
(a.operator->())->b . The rule is then applied recursively to the new
instance of ->. It was designed this way precisely to allow the kind of
multilayered proxies you describe.

> Usually one cannot do more than one
> "implicit conversion" in one go.

There are no implicit conversions anywhere in your example.

> One can seemingly extend the example to further layers of
> indirection, e.g.

Indeed.
--
With best wishes,
Igor Tandetnik

With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925



Re: "Double dispatching" of operator-> by John

John
Tue Feb 07 07:27:53 CST 2006

"Simon Trew" <noneofyour@business.guv> wrote in message
news:eZ2HPT%23KGHA.1192@TK2MSFTNGP11.phx.gbl
> Consider the following program:
>
> // P is designed to be some proxy for T
> template<class T>
> struct P
> {
> T* operator ->();
> };
>
> // PP<T> is designed to be some proxy for the proxy P<T>
> template<class T>
> struct PP
> {
> P<T> operator ->();
> };
>
> // Any old class
> class C
> {
> int f();
> };
>
> int main(int, char**)
> {
> PP<C> c;
> return c->f();
> }
>
> This compiles successfully on MSVC6, MSVC8 (beta 2) and Comeau Online.
>
> How is C::f() found? (it is indeed being found, as can be confirmed by
> attempting to link it.) By what rule does "c->f()" get interpreted as
> "c.operator->().operator->().f()"? Usually one cannot do more than one
> "implicit conversion" in one go. Is there a special rule, or indeed a
> general rule that I am missing here?
>
> One can seemingly extend the example to further layers of
> indirection, e.g.
> template<class T>
> struct PPP
> {
> PP<T> operator ->();
> };
>
> template<class T>
> struct PPPP
> {
> PPP<T> operator ->();
> };
>
> then change PP<C> in main to be PPPP<C>.

I think that operator-> has always been a special case. Consider the
following simpler example:

#include <iostream>
using namespace std;

template<class T>
struct P
{
P(T * arg) : ptr(arg)
{}
T*ptr;
T*operator->()
{ return ptr; }
};


struct C
{
void f()
{ cout << "f function\n";}
};


int main()
{
C c;
P<C> p(&c);
p->f();
}

Consider the effect of p->. Plainly this invokes operator->() of P. Treating
this call as inline, we end up with:

ptr f();

i.e., if operator->() was a normal operator, then p-> would get replaced
with ptr, but the -> operator would not get called on ptr. Yet plainly it
*does* get called. Thus operator->() does more than simply return a pointer,
in spite of the fact that that is what it is defined to do. It returns a
pointer and then calls the built-in -> operator on that returned pointer.
More generally, it returns either a pointer or a class object, and calls ->
or operator->(), respectively on that pointer/object. The chaining
possibilities follow directly from this.

--
John Carson






Re: "Double dispatching" of operator-> by Simon

Simon
Tue Feb 07 07:45:51 CST 2006

>> How is C::f() found? (it is indeed being found, as can be confirmed by
>> attempting to link it.) By what rule does "c->f()" get interpreted as
>> "c.operator->().operator->().f()"?
>
> That would be c.operator->().operator->()->f()

Sorry, yes.

> An expression a->b where a is not a pointer is interpreted as
> (a.operator->())->b . The rule is then applied recursively to the new
> instance of ->. It was designed this way precisely to allow the kind of
> multilayered proxies you describe.

Fine-- I would just like to find where in the standard this is defined. It
doesn't *seem* to be covered by 13.3.1.1.2 - Call to object of class type
[over.call.object].

>> Usually one cannot do more than one
>> "implicit conversion" in one go.
>
> There are no implicit conversions anywhere in your example.

I know. I was just trying to make an analogy-- seems I wasn't clear enough.



Re: "Double dispatching" of operator-> by John

John
Tue Feb 07 07:50:40 CST 2006

"Simon Trew" <noneofyour@business.guv> wrote in message
news:OUh2Qz%23KGHA.2336@TK2MSFTNGP12.phx.gbl
>>> How is C::f() found? (it is indeed being found, as can be confirmed
>>> by attempting to link it.) By what rule does "c->f()" get
>>> interpreted as "c.operator->().operator->().f()"?
>>
>> That would be c.operator->().operator->()->f()
>
> Sorry, yes.
>
>> An expression a->b where a is not a pointer is interpreted as
>> (a.operator->())->b . The rule is then applied recursively to the new
>> instance of ->. It was designed this way precisely to allow the kind
>> of multilayered proxies you describe.
>
> Fine-- I would just like to find where in the standard this is
> defined. It doesn't *seem* to be covered by 13.3.1.1.2 - Call to
> object of class type [over.call.object].
>

Section 13.5.6


--
John Carson



Re: "Double dispatching" of operator-> by Simon

Simon
Tue Feb 07 07:54:33 CST 2006

"John Carson" <jcarson_n_o_sp_am_@netspace.net.au> wrote in message
news:%23UDvVp%23KGHA.532@TK2MSFTNGP15.phx.gbl...

[example snipped]

> i.e., if operator->() was a normal operator, then p-> would get replaced
> with ptr, but the -> operator would not get called on ptr. Yet plainly it
> *does* get called. Thus operator->() does more than simply return a
> pointer,
> in spite of the fact that that is what it is defined to do. It returns a
> pointer and then calls the built-in -> operator on that returned pointer.
> More generally, it returns either a pointer or a class object, and
> calls -> or operator->(), respectively on that pointer/object. The
> chaining possibilities follow directly from this.

OK I think I understand that. I see that it says this in 13.5.6 [over.ref]
which I obviously hadn't read carefully enough. I'm still a little confused
about how the chaining follows from that, it says:

<quot>
-1- operator-> shall be a non-static member function taking no parameters.
It implements class member access using ->

postfix-expression -> id-expression

An expression x->m is interpreted as (x.operator->())->m for a class object
x of type T [...]
</quot>

I think my confusion here lies in the fact that the second -> can also be an
overloaded operator->(), whereas I'd assumed it "always" meant the built-in
operator->. It's not very explicit, is it?



Re: "Double dispatching" of operator-> by Simon

Simon
Tue Feb 07 08:00:26 CST 2006

Actually small point-- it compiles if "class C" is changed to "struct C"
(otherwise f() is inaccessible). Cut&paste error, sorry.



Re: "Double dispatching" of operator-> by John

John
Tue Feb 07 08:26:24 CST 2006

"Simon Trew" <noneofyour@business.guv> wrote in message
news:OXp8H4%23KGHA.3100@tk2msftngp13.phx.gbl
> "John Carson" <jcarson_n_o_sp_am_@netspace.net.au> wrote in message
> news:%23UDvVp%23KGHA.532@TK2MSFTNGP15.phx.gbl...
>
> [example snipped]
>
>> i.e., if operator->() was a normal operator, then p-> would get
>> replaced with ptr, but the -> operator would not get called on ptr.
>> Yet plainly it *does* get called. Thus operator->() does more than
>> simply return a pointer,
>> in spite of the fact that that is what it is defined to do. It
>> returns a pointer and then calls the built-in -> operator on that
>> returned pointer. More generally, it returns either a pointer or a
>> class object, and calls -> or operator->(), respectively on that
>> pointer/object. The
>> chaining possibilities follow directly from this.
>
> OK I think I understand that. I see that it says this in 13.5.6
> [over.ref] which I obviously hadn't read carefully enough. I'm still
> a little confused about how the chaining follows from that, it says:
>
> <quot>
> -1- operator-> shall be a non-static member function taking no
> parameters. It implements class member access using ->
>
> postfix-expression -> id-expression
>
> An expression x->m is interpreted as (x.operator->())->m for a class
> object x of type T [...]
> </quot>
>
> I think my confusion here lies in the fact that the second -> can
> also be an overloaded operator->(), whereas I'd assumed it "always"
> meant the built-in operator->. It's not very explicit, is it?

You are meant to apply the rule recursively. I guess this is not as explicit
as it might be, like most of the Standard. The way it works is as follows.
Applying the passage from the standard,

c->f();

becomes

(c.operator->())->f();

c.operator->() returns a P<C> temporary. Substituting it in gives:

(P<C>())->f();

Now we apply the original rule in terms of x->m with P<C>() as x. This
gives:

(P<C>().operator->())->f();

P<C>().operator->() returns a C* pointer, which I will denote by pc.
Substituting it in gives:

pc->f();


--
John Carson




Re: "Double dispatching" of operator-> by Simon

Simon
Tue Feb 07 09:04:02 CST 2006

"John Carson" <jcarson_n_o_sp_am_@netspace.net.au> wrote in message
news:OFZ1LK$KGHA.3984@TK2MSFTNGP14.phx.gbl...
> "Simon Trew" <noneofyour@business.guv> wrote in message
> news:OXp8H4%23KGHA.3100@tk2msftngp13.phx.gbl
>> "John Carson" <jcarson_n_o_sp_am_@netspace.net.au> wrote in message
>> news:%23UDvVp%23KGHA.532@TK2MSFTNGP15.phx.gbl...
>>
>> [example snipped]

> You are meant to apply the rule recursively.

Yes, I realised that.

> I guess this is not as explicit as it might be, like most of the Standard.

Yes :)

Thanks.

S.



Re: "Double dispatching" of operator-> by Axter

Axter
Tue Feb 07 14:06:27 CST 2006

"John Carson" wrote:
> You are meant to apply the rule recursively. I guess this is not as explicit
> as it might be, like most of the Standard. The way it works is as follows.
> Applying the passage from the standard,
>
> c->f();
>
> becomes
>
> (c.operator->())->f();
>
> c.operator->() returns a P<C> temporary. Substituting it in gives:
>
> (P<C>())->f();
>
> Now we apply the original rule in terms of x->m with P<C>() as x. This
> gives:
>
> (P<C>().operator->())->f();
>
> P<C>().operator->() returns a C* pointer, which I will denote by pc.
> Substituting it in gives:
>
> pc->f();
>

Does this also apply to operator*() ?
I tried to do a proxy class one time, and could only get operator->() to
work recursively.
operator*() failed to compile recursively.


-----------------------------------------------
Top Ten Expert in Experts-Exchange C++ and MFC topic area.
http://www.experts-exchange.com/Cplusplus
------------------------------------------------



Re: "Double dispatching" of operator-> by Igor

Igor
Tue Feb 07 14:55:26 CST 2006

Axter <Axter@discussions.microsoft.com> wrote:
> "John Carson" wrote:
>> You are meant to apply the rule recursively.
>
> Does this also apply to operator*() ?

No. operator* does not return a pointer, it returns an actual value
which itself does not usually implement operator*. So you cannot apply
the rule recursively.
--
With best wishes,
Igor Tandetnik

With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925



Re: "Double dispatching" of operator-> by John

John
Tue Feb 07 21:12:28 CST 2006

"Axter" <Axter@discussions.microsoft.com> wrote in message
news:6FA87C1D-D6EF-4EDA-8178-9D2F9087FF24@microsoft.com
> "John Carson" wrote:
>> You are meant to apply the rule recursively. I guess this is not as
>> explicit as it might be, like most of the Standard. The way it works
>> is as follows. Applying the passage from the standard,
>>
>> c->f();
>>
>> becomes
>>
>> (c.operator->())->f();
>>
>> c.operator->() returns a P<C> temporary. Substituting it in gives:
>>
>> (P<C>())->f();
>>
>> Now we apply the original rule in terms of x->m with P<C>() as x.
>> This gives:
>>
>> (P<C>().operator->())->f();
>>
>> P<C>().operator->() returns a C* pointer, which I will denote by pc.
>> Substituting it in gives:
>>
>> pc->f();
>>
>
> Does this also apply to operator*() ?
> I tried to do a proxy class one time, and could only get operator->()
> to work recursively.
> operator*() failed to compile recursively.
>


As indicated in my first post, operator->() does more than the user defines
it to do. In addition to returning a pointer or class object, as per the
user's definition, it calls the built-in -> operator or the class's
operator->(), respectively, on the returned pointer/object. This is why you
get chaining.

operator*, by contrast, does exactly what the user defines it to do and no
more; it does not call the dereferencing operator on the returned object.
Thus if you want multi-level dereferencing, you have to manually supply the
dereferencing calls for each level yourself.

--
John Carson