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.