gregk@cbnewsm.att.com (gregory.p.kochanski) (12/05/90)
In article <CLINE.90Dec4102440@cheetah.ece.clarkson.edu>, cline@cheetah.ece.clarkson.edu (Marshall Cline) writes: > Allow me to propose a simpler and more comprehensive solution than resolving > funcalls based on the const-ness of their return types. Consider a returned > reference from an `inspector' such as int& operator[](int). The general > problem is to `capture' how this returned reference is used in the *caller's* > context. The poster wanted to differentiate between `accessing the value of' > and `used in LHS of an assignment operator', as exemplified by > arr[0] = arr[1]; > ^^^^^^----suppose this can be done quickly > ^^^^^^-------------and suppose this is more expensive > > The cleaner solution to this more general problem is to return a light-weight > object which simulates a reference. > > class intRef { > int& ref; > public: > intRef& operator=(int x) { expensive_check(x); ref=x; return *this; } > operator int() { return ref; } //access is cheap > IntRef(int& r) : ref(r) { } > }; This *is* nice (and probably is the way to go), but 1) you also have to define += -= *= /= |= <<= ... and the address operator, in order to make it a reasonable simulation. 2) Many practical situations will require a class which has more than just a reference. You may also need pointers to the original array, and perhaps a second (or third...) subscript. A three-dimensional array needs *three* helper classes. Real life situations (real life for numeric computations, that is) can end up with a substantial amount of overhead. If your calculations already take hours, even relatively small amounts of overhead (like copying a 3 member structure and a pointer operation) can be painful. 3) It does lead to a proliferation of trivial helper classes which could easily make your code totally unintelligible. Especially in higher dimensional cases. 4) It's not quite a perfect simulation, because you can only have one user-defined conversion, which is used up in converting from intRef to int. If you have a user-defined conversion from int to SomethingElse, it will have to be explicitly called. Greg Kochanski gpk@att.physics.com AT&T Physics Research... Physics for fun and profit.
mat@mole-end.UUCP (Mark A Terribile) (12/07/90)
In article <1990Dec4.231722.10425@cbnewsm.att.com>, gregk@cbnewsm.att.com (gregory.p.kochanski) writes: > In article <CLINE.90Dec4102440@cheetah.ece.clarkson.edu>, cline@cheetah.ece.clarkson.edu (Marshall Cline) writes: >> ... The general >>problem is to `capture' how this returned reference is used in the *caller's* >>context. The poster wanted to differentiate between `accessing the value of' >> and `used in LHS of an assignment operator', as exemplified by >> arr[0] = arr[1]; >> ^^^^^^----suppose this can be done quickly >> ^^^^^^-------------and suppose this is more expensive >>The cleaner solution to this more general problem is to return a light-weight >>object which simulates a reference. . . . > This *is* nice (and probably is the way to go), but > 1) you also have to define += -= *= /= |= <<= ... and the address operator, > in order to make it a reasonable simulation. > 2) Many practical situations will require a class which has more than just > a reference. You may also need pointers to the original array, > and perhaps a second (or third...) subscript. A three-dimensional > array needs *three* helper classes. Real life situations (real life > for numeric computations, that is) can end up with a substantial > amount of overhead. If your calculations already take hours, > even relatively small amounts of overhead (like copying a 3 member > structure and a pointer operation) can be painful. > 3) It does lead to a proliferation of trivial helper classes which > could easily make your code totally unintelligible. Especially > in higher dimensional cases. This is a terrific application for templates. Combining templates with classes-within-classes (which should allow several classes to be created by one template expansion) should improve (1), (3), and the first part of (2). The efficiency considerations in (2) can be handled by such pragmatic things as careful design of the intermediate types so that no more than one or two machine words must be copied, the use of inlines, the use of highly optimizing compilers (either large-window peepholes, `global' optimizers, or really good dataflow optimization). I grant that this makes the compiler's task enormous, but there is no `magic' step which represents intelligence. (Does anyone have a comparison between the size of a C++ compiler's source and the size of a full PL1 implementation's compiler source?) I also note that such templates will create very serious needs of their own. First, they will create a need for a way to write about them and describe their behavior. If anyone has tried to figure out how the stream I/O macros for the declaration and use of `manipulators' are to be invoked, he will understand the difficulty. This has at least the usual ramifications: the code must be written so that its purpose (at least) is simple, clear, and easy to state, there must be a more-or-less standard way of summarizing the operations possible (man page form), etc., > 4) It's not quite a perfect simulation, because you can only have one > user-defined conversion, which is used up in converting from > intRef to int. If you have a user-defined conversion from int to > SomethingElse, it will have to be explicitly called. Well, using the template solution, you can't, anyway. The real killer is the `one built-in + one user-defined' restriction (in its various forms) because it makes it hard to DESIGN types to be used in expressions that depend upon conversions which may be `consumed' by the context in which the expression are seen ... and note that E&S (at least) limits the searching for overloads that may be performed around certain kinds of templates. The second very serious need surrounding templates is for some sort of meta- language to express variations on the behavior of a template. It should be possible to create a member that can be accessed using Class:: but not by default so that MI-in-templates ( T<class a, class b> class Thomthing : public a ... ) does not get hung up on hard-to-predict namespace clashes. Moreover, when one class or template is `improved' by the people who provide and maintain it, it should not introduce namespace clashes into existing code ... There's a LOT of room for work here, and a LOT of work to be done before we get any sort of rules on good style and good idiom. But there's also an awful lot of potential. -- (This man's opinions are his own.) From mole-end Mark Terribile