🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.std.c++
Article #573

Re: "->" == "." proposal

#573
From: Jim ADCOCK
Date: Mon, 05 Nov 1990 19:53
340 lines
11191 bytes
Below find a copy of the request for consideration that I sent to Lenkov
and Miller.  Like I said, I've received no form of acknowledgement, so I
can't say where this is at right now.

----

Dmitry Lenkov
HP California Language Lab
19447 Pruneridge Avenue MS 47 LE
Cupertino CA 95014

William M. Miller
GLockenspiel Ltd.
PO Box 366
SudBury, MA 01776-0003

						Oct 8, 1990


Request for Consideration:  Overloadable Unary 
operator.()


I humbly request the C++ standardization committee 
consider allowing overloadable operator.(), operator 
to work analogous to overloaded operator->().

Discussion as follows:

With few exceptions, C++ allows all its operators be 
overloaded.  The few exceptions are:  .  [dot,]  .*  
[dot star,]  :: [colon colon,] and ?: [binary 
selection.]  The commentary on page 330 ARM, gives 
the following "explanation" :

The reason for disallowing the overloading of ., .*, 
and :: is that they already have a predefined meaning 
for objects of any class as their first operand.  
Overloading of ?: simply didn't seem worthwhile. 

I agree I can't see any worthwhile reason for 
overloading ?:, but the reason given for disallowing 
the other operators cannot hold, because there is 
already a counterexample:  unary operator& already 
had a predefined meaning, yet it is overloadable.  

Three questions to be answered in considering 
operator.() as a candidate for overloading are: 1) 
would doing so cause any great problem?  2) are there 
any compelling reasons to allow it?  3) what 
overloadings of operator.() should be permitted?  I 
claim these questions can be easily answered as 
follows: 1) allowing operator.() to be overloaded 
causes no great problems.  2)  there are compelling 
reasons to allow it  and 3)  unary operator 
overloading analogous to what is permitted of unary 
operator->() should be permitted.  IE unary member 
overloaded operator.(), which can be called with an 
object or reference of the class it is defined in, or 
derived class, on the left hand side, returning a 
reference or object of a class to which . can be 
applied again.

Discussion of these claims:

"Allowing operator.() to be overloaded causes no 
great problems."

Unary operator& demonstrates that there is no problem 
overloading a function for which there is already a 
pre-defined meaning.  The implementation of 
operator.() in other respects is similar to operator-
>() which also has been successfully implemented by 
several compilers, demonstrating that no new 
technology is required to implement unary 
operator.().  Like operator&, and operator->(), 
operator.() is never invoked except on a lhs object 
of a class explicitly overloading operator.(), thus 
no existing code can be affected by this change.

Some people have expressed concern that if 
operator.() is overloadable, then how does one 
specify member selection of that class members 
themselves necessary to implement operator.() ?  In 
practice, this does not prove to be a problem.  
Operator.() is invoked only in situations where . 
[dot] is explicitly used, and when writing smart 
reference classes, proxies, and other simple classes 
one typically accesses members via "implied this->", 
thus one doesn't use . [dot].  In situations where 
one would normally use . [dot], such as when a class 
instance is passed as a parameter to a member 
function, getting an overloaded operator.() can be 
sidestepped via pointer syntax:  use (&ob)->member, 
rather than ob.member.  People next complain that 
this means it will be difficult to simultaneously 
overload operator->() and operator.() to which I 
reply: Thank God!  We don't need classes that try to 
act simultaneously as pointers and references!  
That's the whole point of allowing operator.() to be 
overloaded:  so that objects that act like pointers 
can use pointer syntax, and objects that act like 
references can follow reference syntax! 

"There are compelling reasons to allow it"

Overloading operator.() is necessary in practice to 
allow "smart reference" classes similar to the "smart 
pointer" classes permitted by overloading operator-
>().  Overloading operator->() to access objects 
following reference semantics is pretty workable:

RefCntPtr pob;

pob = pobOther;
pob->DoThis();
pob->DoThat();

However, if the object needs to follow value 
semantics, then this solution becomes onerous:

RefCntHugeIntPtr pA, pB, pC;
//....
*pC = *pA + *pB;
int digit104 = (*pC)[104];
pC->truncateNdigits(100);

What you really want to be able to do for objects 
that require value semantics is create smart 
references as follows:

RefCntHugeIntRef a, b, c;
/....
c = a + b
int digit104 = c[104];
c.truncateNDigits(100);

Another common case where you'd rather have 
overloaded operator.() rather than operator->() is in 
creating proxy classes.  The proxy class just 
forwards messages to its destination classes.  A 
proxy class can be used in many ways.  An example is 
a proxy member, allowing a runtime decision of the 
actual implementation of that member.  Or a class can 
even inherit from a proxy, allowing the behavior of 
its parent be specified at run.
[Behavior, but not protocol, that is]  Of course, 
pointer syntax can be consistently used for
these proxy cases, but the underlying implication is 
that object instances are being created dynamically 
on the heap, not statically,  nor on the stack.

Note, that at a relatively high leve of pain, classes 
obeying reference semantics can already be created:  
One simply writes a class that contains a pointer 
that can be assigned to the forwarding object, and 
write an inline forwarder function for each and every 
member function in the protocol:

class FooProxy
{
    foo* pfoo;
public:
    FooProxy(foo* pfooT) : pfoo(pfooT) {}
    void DoThis() { pfoo -> DoThis(); }
    void DoThat(int i) { pfoo -> DoThat(i); }
    int ReturnInt() { pfoo -> ReturnInt(); }
//  ....
    void NthMemberOfProtocol() {pfoo -> 
NthMemberOfProtocol();}
};

Needless to say, when most class writers are faced 
with the prospect of manually writing a forwarding 
function for each and every function in a protocol, 
they don't!   They punt instead, and overload 
operator->(), even when the rest of their class 
follows value semantics.  Thus, class users end up 
having to guess whether to use . or -> as the member 
selector in every context.  If operator.() is 
overloadable as well as operator->(), then customers 
can learn the simple convention:  "Use . whenever 
dealing with object obeying value semantics, use -> 
whenever dealing with objects obeying reference 
semantics."

In short, lacking operator.(), class writers are 
forced to violate convention meaning of operator->().  
Instead, we should enable a complete set of 
overloadable operators, so that class writers can 
maintain historical meanings and usages of these 
operators.

I therefore ask due consideration be given to 
allowing operator.() to be overloaded analogous to 
operator->().  I suspect that the committee should 
then consider also whether operator.*() be 
overloadable analogous to operator->*().  However, I 
am not asking for that, since I do not consider 
myself sufficiently experienced with member pointers 
to be aware of the ramifications.  Let someone else 
propose the necessary changes for operator.*(), if 
they so choose.

The necessary changes to the Annotated Reference 
Manual to support operator.() are listed below.  I 
list the changes necessary in ARM, rather than the 
product reference manual, since the changes necessary 
to ARM are a pure superset of the changes necessary 
to the product reference manual.

Jim Adcock, Oct. 8, 1990

_______________________

Section 7.2.1c

Compiler vendors would need to add a convention for 
encoding operator. [dot.]  But this is not an issue 
for the standardization effort.

Section 12.3c

Add the following table entry:

. [operator dot]	|  yes     |  yes    |  yes    |        
member          |  no

Chapter 13, page 307, line 6, change to:

and unary class member accessors -> and . [dot]) when 
at least one operand is a class object.

Page 330:

operator: one of ....  -- add . [dot] to the list

The following operators cannot be overloaded: .... 
remove . [dot] from the list.

The reason for disallowing the overloading of ., .*, 
and :: is that they already have a predefined meaning 
for objects of any class as their first operand.  
Overloading of ?: simply didn't seem worthwhile. 

Change To:

The reason for disallowing the overloading of :: and 
?: is that it simply didn't seem worthwhile. 


Section 13.4.6

Add the following text:

Class member access using .  [dot]

	primary-expression  . primary-expression

is considered a unary operator.  An expression x.m is 
interpreted as (x.operator.()).m for a class object 
x.  It follows that operator.() must return either a 
reference to a class or an object of or a reference 
to a class for which operator.() is defined.  
operator.() must be a nonstatic member function.

___________________________

Commentary:

Note that Ellis and Stroustrup's annotated notes on 
page 337 could have been just as well written as 
follows:

Consider creating classes of object intended to 
behave like what one might call "smart references" -- 
references that do some additional work, like 
updating a use counter on each access through them.

struct Y { int m; };

class Yref {
    Y* p;
    // information
public:
    Yref(const char* arg);
    Y& operator.();
};

Yref::Yref(const char* arg)
{
    p = 0;
    // store away information
    // based on 'arg'
}

Y& Yref::operator.()
{
    if (p) {
        // check p
        // update information
    }
    else {
        // initialize p using information
    }
    return *p;
}

Class Yref's . [dot] operator  could be used as 
follows:

void f(Yref y, Yref& yr, Yref* yp)
{
    int i = y.m;		// y.operator.().m
    i = yr.m;		// yr.operator.().m
    i = yp.m;		// error: Yref does not have a 
member m
}

Class member access is a unary operator.  An 
operator.() must return something that can be used as 
an object or reference.  

Note that  there is nothing special about the binary 
operator .*  [dot star.]  The rules in this section 
apply only to . [dot].

End Commentary.
___________________________


Thank you for your consideration, and please inform 
me of your decisions.


						James L. Adcock
						Microsoft 
						One Microsoft Way
						Redmond, WA 98052
						
Technical Details
Message-ID: <58814@microsoft.UUCP>
Path: arc1-ca.pugleaf.net!utzoo!attcan!uunet!microsoft!jimad
References: <52390002@hpcupt1.cup.hp.com> <27304544.3658@marob.masa.com>

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!utzoo!attcan!uunet!microsoft!jimad
From: jimad@microsoft.UUCP (Jim ADCOCK)
Newsgroups: comp.std.c++
Subject: Re: "->" == "." proposal
Message-ID: <58814@microsoft.UUCP>
Date: 5 Nov 90 19:53:32 GMT
References: <52390002@hpcupt1.cup.hp.com> <27304544.3658@marob.masa.com>
Reply-To: jimad@microsoft.UUCP (Jim ADCOCK)
Organization: Microsoft Corp., Redmond WA
Lines: 340