[comp.lang.c++] Better soln than fn resolution based on const-ness of return type

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