[comp.lang.c++] Distinguishing lvalue/rvalue operator

horstman@mathcs.sjsu.edu (Cay Horstmann) (03/04/91)

I recently suggested to distinguish lvalue and rvalue operator[] by 
passing in an additional int for one of them, just like pre- and
post operator++ are distinguished in 2.1. Joe Buck suggested this is
unnecessary--one can have operator[] return an access class with 
     X& Access::operator=( X& ); // for lvalue usage
     Access::operator X(); // for rvalue usage
I just tried this out and it broke my code right away. The line
     a[i].print();
didn't compile--print() is not a member function of Access! Joe's suggestion
works fine for non-class types because the type conversion Access->X will
be executed, except before . and ->. Actually, I could surely come up
with a disgusting scenario where the overloading resolution rules give
unexpected results when one of the arguments of a function f with multiple
versions is f( ... a[i] ... ).

I repeat my call for two versions of this operator. William Miller had three
arguments against it.
(1) There is an alternate solution.
    I hope my argument against this is conclusive. 
(2) It breaks existing code
    Isn't it possible to map both lvalue and rvalue [] to the same 
    operator[](int) if an operator[](int,int) is not present?
(3) It makes operator[] special.
    I agree. On the other hand, it may well reasonable to have some operators
    special. The assignment operator= is clearly special. So is operator->.
    In fact, it probably makes sense to have both unary operator* and 
    operator[] lvalue/rvalue aware because their C analogs are. In contrast,
    operator++ shouldn't have this awareness because the C analog doesn't
    either. 
Two comments. The problem I encountered could be solved by overloading
operator. . <- the first is a dot operator, the second a sentence terminator.
I realize that the trick of supplying a second int argument isn't going to
work for distinguishing lvalue/rvalue occurrences of unary operator*. 

Cay

wmm@world.std.com (William M Miller) (03/04/91)

horstman@mathcs.sjsu.edu (Cay Horstmann) writes:
> I recently suggested to distinguish lvalue and rvalue operator[] by
> passing in an additional int for one of them, just like pre- and
> post operator++ are distinguished in 2.1. Joe Buck suggested this is
> unnecessary--one can have operator[] return an access class with
>      X& Access::operator=( X& ); // for lvalue usage
>      Access::operator X(); // for rvalue usage
> I just tried this out and it broke my code right away. The line
>      a[i].print();
> didn't compile--print() is not a member function of Access!

So what's the big deal?  Just add an inline Access::print() member function
that forwards the call to X::print().  Admittedly, that could be a pain if
you're working with an X that has dozens of members (and, at least for
current compilers, there will be some runtime overhead involved in the
initialization of references to data members that are to be accessed
transparently), but I haven't seen anything you want to do that's
prohibitively hard or impossible to do in the current language.  In my
experience, this problem doesn't arise all that often, and if you do run
into it all the time, it would be pretty straightforward to program a filter
that automatically produced an Access class for a given X.

> I repeat my call for two versions of this operator. William Miller had three
> arguments against it.
> (1) There is an alternate solution.
>     I hope my argument against this is conclusive.

I don't think so.

> (2) It breaks existing code
>     Isn't it possible to map both lvalue and rvalue [] to the same
>     operator[](int) if an operator[](int,int) is not present?

Actually, my argument was not that it breaks existing code, only that it
would have to break existing code if it were made genuinely parallel with
the treatment of operator++(), and that making it different from
operator++() introduces a rather gratuitous additional complexity.  It's
certainly possible to treat it as you suggested; it is also possible to
change the definition of how operator++() is treated to work the same way.

> (3) It makes operator[] special.
>     I agree. On the other hand, it may well reasonable to have some operators
>     special. The assignment operator= is clearly special. So is operator->.
>     In fact, it probably makes sense to have both unary operator* and
>     operator[] lvalue/rvalue aware because their C analogs are. In contrast,
>     operator++ shouldn't have this awareness because the C analog doesn't
>     either.

I'm not sure what you mean by C analogs of operator[]() and unary
operator*() having "awareness" of lvalueness -- the C analogs always return
lvalues unconditionally; the results are only converted into rvalues when
used in a non-lvalue context.  On the other hand, the C++ operators like
operator++() do return lvalues or rvalues, depending on their use and
arguments.

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com

jbuck@galileo.berkeley.edu (Joe Buck) (03/04/91)

In article <1991Mar3.202134.17812@mathcs.sjsu.edu>, horstman@mathcs.sjsu.edu (Cay Horstmann) writes:
|> I recently suggested to distinguish lvalue and rvalue operator[] by 
|> passing in an additional int for one of them, just like pre- and
|> post operator++ are distinguished in 2.1. Joe Buck suggested this is
|> unnecessary--one can have operator[] return an access class with 
|>      X& Access::operator=( X& ); // for lvalue usage
|>      Access::operator X(); // for rvalue usage
|> I just tried this out and it broke my code right away. The line
|>      a[i].print();
|> didn't compile--print() is not a member function of Access! Joe's suggestion
|> works fine for non-class types because the type conversion Access->X will
|> be executed, except before . and ->.

The helper class, Access in your code, is an example of a "smart reference".
For smart pointers, -> can be overloaded to do the right thing.  That leaves
as the only hole the fact that "." cannot be redefined for smart references.
Now this is an extension I would support, since it is clean and makes the
language more orthogonal.

Semantically, what is being returned by [] is a reference (though in some
cases it needs to be a tricky one).

|> with a disgusting scenario where the overloading resolution rules give
|> unexpected results when one of the arguments of a function f with multiple
|> versions is f( ... a[i] ... ).

There should be no unexpected results in this case, because there is one
and only one type conversion operator for class Access.  The only problem
that cannot be worked around is the . operator.

|> I repeat my call for two versions of this operator. William Miller had three
|> arguments against it.
|> (1) There is an alternate solution.
|>     I hope my argument against this is conclusive. 

It shows a problem with it, but other than that problem (which can be solved
for the previously proposed extension, smart references to complement smart
pointers), it works well and is used extensively in class libraries.  You
cannot say a[i].print() but you can say X(a[i]).print(), which I will grant
is ugly.

|> (2) It breaks existing code
|>     Isn't it possible to map both lvalue and rvalue [] to the same 
|>     operator[](int) if an operator[](int,int) is not present?
|> (3) It makes operator[] special.
|>     I agree. On the other hand, it may well reasonable to have some operators
|>     special. The assignment operator= is clearly special. So is operator->.
|>     In fact, it probably makes sense to have both unary operator* and 
|>     operator[] lvalue/rvalue aware because their C analogs are. In contrast,
|>     operator++ shouldn't have this awareness because the C analog doesn't
|>     either. 
|> Two comments. The problem I encountered could be solved by overloading
|> operator. . <- the first is a dot operator, the second a sentence terminator.
|> I realize that the trick of supplying a second int argument isn't going to
|> work for distinguishing lvalue/rvalue occurrences of unary operator*. 

All the problems you describe can be eliminated by overloading operator. .
The semantics are exactly analogous to overloading operator-> .  You have
shown a lack in the language, but the solution you propose is weaker and
uglier than a "smart reference" solution, which also gets rid of the problems
with unary *.

--
Joe Buck
jbuck@galileo.berkeley.edu	 {uunet,ucbvax}!galileo.berkeley.edu!jbuck	

horstman@mathcs.sjsu.edu (Cay Horstmann) (03/04/91)

I was thinking more about Joe Buck's suggestion to make the distinction
of lvalue/rvalue operator[] by returning an access class, and found another
snag. Suppose you have a variable size integer array and have operator[]
return an instance of class Access with functions
     Access::operator=(int)
     Access::operator int
as suggested. Then the statement
     a[i]++
will not execute. Really, to preserve the "look and feel" of the standard
operator[], it seems that it must return a reference. Or is there some clever
way to overcome this problem?

Cay

jimad@microsoft.UUCP (Jim ADCOCK) (03/05/91)

In article <1991Mar3.202134.17812@mathcs.sjsu.edu> horstman@mathcs.sjsu.edu (Cay Horstmann) writes:
|I repeat my call for two versions of this operator. William Miller had three
|arguments against it.
...
|Two comments. The problem I encountered could be solved by overloading
|operator. . <- the first is a dot operator, the second a sentence terminator.

Given that the ANSI-C++ committee is already considering adding overloaded
operator dot, and given that allowing overloaded operator dot would
remove a special case from the language, whereas distinguishing lvalue
and rvalue operator[] would add a special case to the language, would
you drop your request for lvalue/rvalue distinguished operator[] if
overloaded operator-dot were permitted?

wmm@world.std.com (William M Miller) (03/05/91)

horstman@mathcs.sjsu.edu (Cay Horstmann) writes:
> I was thinking more about Joe Buck's suggestion to make the distinction
> of lvalue/rvalue operator[] by returning an access class, and found another
> snag. Suppose you have a variable size integer array and have operator[]
> return an instance of class Access with functions
>      Access::operator=(int)
>      Access::operator int
> as suggested. Then the statement
>      a[i]++
> will not execute.

Of course not.  (You wouldn't want it to, anyway -- presumably whatever you
do in operator=() to handle lvalue access also needs to be done for ++, no?)
The "Access" class is really acting as an enhanced functionality surrogate
for the addressed member (enhanced functionality because it's supposedly
doing different things for lvalue and rvalue accesses).  That means that
anything you want to do with the accessed array member, you must be able to
do with an object of class Access.  If you want to use ++, you need an
Access::operator++().  If you want to do ".print()," as in your earlier
posting, you need an Access::print().  It's really very straightforward --
you look at what you need to do with selected array elements and you put the
appropriate support in the corresponding Access class.

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com

horstman@mathcs.sjsu.edu (Cay Horstmann) (03/07/91)

In article <1991Mar5.021423.22117@world.std.com> wmm@world.std.com (William M Miller) writes:
>horstman@mathcs.sjsu.edu (Cay Horstmann) writes:
>> I was thinking more about Joe Buck's suggestion to make the distinction
>> of lvalue/rvalue operator[] by returning an access class, and found another
>> snag. Suppose you have a variable size integer array and have operator[]
>> return an instance of class Access with functions
>>      Access::operator=(int)
>>      Access::operator int
>> as suggested. Then the statement
>>      a[i]++
>> will not execute.
>
>Of course not.  (You wouldn't want it to, anyway -- presumably whatever you
>do in operator=() to handle lvalue access also needs to be done for ++, no?)
>The "Access" class is really acting as an enhanced functionality surrogate
>for the addressed member (enhanced functionality because it's supposedly
>doing different things for lvalue and rvalue accesses).  That means that
>anything you want to do with the accessed array member, you must be able to
>do with an object of class Access.  If you want to use ++, you need an
>Access::operator++().  If you want to do ".print()," as in your earlier
>posting, you need an Access::print().  It's really very straightforward --
>you look at what you need to do with selected array elements and you put the
>appropriate support in the corresponding Access class.
>
Not as straightforward as you say. I am writing a template. I have no way
of knowing what methods X includes, so I cannot manually replicate them
in the access class. 

Cay

jimad@microsoft.UUCP (Jim ADCOCK) (03/08/91)

In article <1991Mar4.031900.16827@world.std.com> wmm@world.std.com (William M Miller) writes:
|> I just tried this out and it broke my code right away. The line
|>      a[i].print();
|> didn't compile--print() is not a member function of Access!
|
|So what's the big deal?  Just add an inline Access::print() member function
|that forwards the call to X::print().  Admittedly, that could be a pain if
|you're working with an X that has dozens of members (and, at least for
|current compilers, there will be some runtime overhead involved in the
|initialization of references to data members that are to be accessed
|transparently), but I haven't seen anything you want to do that's
|prohibitively hard or impossible to do in the current language.  In my
|experience, this problem doesn't arise all that often, and if you do run
|into it all the time, it would be pretty straightforward to program a filter
|that automatically produced an Access class for a given X.

Yes, you can do this using a C-preprocessor macro hack, or an external
tool, but the resulting code becomes a pain to debug and maintain.

Today, if people want to add N "smart array" classes each with about
M methods in the element class, then people have O(M*N) amount of programming 
to write .

-- unless they hack it using the preprocessor or some other tool to write 
it.  With overloadable operator dot, this effort becomes a much more reasonable
O(N) programming effort.  And [ off the top of my head ] with templates
and overloadable operator dot, it would seem that writing smart array 
classes becomes an O(1) effort -- one template smart array class can be
written that also defines the requisite smart reference class.

Thus, with templates and overloadable operator dot, I'd say C++ "supports"
smart array classes.  Without overloadable operator dot, I'd say C++
doesn't support smart array classes, but rather forces programmers to 
go outside the language.  Seems to me this is reason enough alone to 
add overloadable operator dot to the language.  To me, overloadable 
operator dot seems like a cheap and easy fix to a lot of programming 
problems.  -- Not to mention adding overloadable operator dot just
removes one exceptional case from the language.

jimad@microsoft.UUCP (Jim ADCOCK) (03/08/91)

In article <1991Mar5.021423.22117@world.std.com> wmm@world.std.com (William M Miller) writes:
|That means that
|anything you want to do with the accessed array member, you must be able to
|do with an object of class Access.  If you want to use ++, you need an
|Access::operator++().  If you want to do ".print()," as in your earlier
|posting, you need an Access::print().  

This logic would imply that when implementing a smart pointer class,
one should also be forced to implement SmartPointer::print().  Fortunately,
in C++ today, people implementing smart pointers are not forced to do so.  
I suggest that people implementing smart reference classes should also not 
be forced to do so.