Topic: Reference to temporary?


Author: hendrik@vedge.com (Hendrik Boom)
Date: Mon, 13 Sep 1993 22:26:47 GMT
Raw View
rfg@netcom.com (Ronald F. Guilmette) writes:
: In article <KANZE.93Aug30205847@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
: >In article <1993Aug16.091459.151@mole-end.matawan.nj.us>
: >mat@mole-end.matawan.nj.us writes:
: >
: >|> In article <rfgCBtH20.Ex8@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:
: >
: >|> > I think these facts are relevant in this case.  It is certainly true that
: >|> > there must be *some* code out there (somewhere) which contains expressions
: >|> > of the form:
: >
: >|> >   foo().bar()
: >
: >|> > But is it really all that common?  I rather doubt it.  Would people really
: >|> > need to spend millions of man-hours and zillions of dollars to change all
: >|> > of these (rare?) cases to look like:
: >|> >
: >|> >   TYPE temp = foo();
: >|> >   temp.bar();

There's a big difference in meaning here, depending on whether
  foo() returns a class value,
   and bar is a non-const method of that class,
or
  foo() returns a class value,
   and bar is a const method of that class,
or
  foo() returns a reference to a class.

The first case is cause for many silly programming errors,
and appears to be illegal today.
The second case arises because people want to use functional style
and think about invariant values. "const" is the closest thing to that.
This case causes very few troubles, as long as temporary lifetime
is long enough -- and not much is needed to ensure this.
The third should probably be legal if anything is.


: >
: >That is fine if it is just one function being chained.  But what
: >happens when it is two? three? more?  Each function call becomes an
: >extra statement.
:
: Big deal?  If this is the only rationale for keeping lvalue-unsafe features
: in the language, then I'd say the proponents of lvalue-unsafety are on
: pretty shakey ground.
:
: Look, I never said that lvalue-safety wasn't going to cost anybody anything.
: The question is "Is it worth the price?"  The price is very modest indeed,
: and lvalue-safety can indeed act (like type-safety) as a form of insurance
: against stupid programmer misteaks. ;-)
:
: Do you buy insurance for your car?  For your house?  For your health?  Then
: why not buy a little insurance against stupid programming mistakes?
:
: --
:
: -- Ronald F. Guilmette ------------------------------------------------------
: ------ domain address: rfg@netcom.com ---------------------------------------
: ------ uucp address: ...!uunet!netcom.com!rfg -------------------------------
--
-------------------------------------------------------
Try one or more of the following addresses to reply.
at work: hendrik@vedge.com,  iros1!vedge!hendrik
at home: uunet!ozrout!topoi!hendrik




Author: kanze@us-es.sel.de (James Kanze)
Date: 15 Sep 93 13:28:52
Raw View
In article <1993Sep13.222647.840@vedge.com> hendrik@vedge.com (Hendrik
Boom) writes:

|> There's a big difference in meaning here, depending on whether
|>   foo() returns a class value,
|>    and bar is a non-const method of that class,
|> or
|>   foo() returns a class value,
|>    and bar is a const method of that class,
|> or
|>   foo() returns a reference to a class.

|> The first case is cause for many silly programming errors,
|> and appears to be illegal today.

I think most compilers accept it.  Why do you say it appears to be
illegal?   (I can also think of some good uses, again supposing a
sufficiently long lifetime of temporaries.)

|> The second case arises because people want to use functional style
|> and think about invariant values. "const" is the closest thing to that.
|> This case causes very few troubles, as long as temporary lifetime
|> is long enough -- and not much is needed to ensure this.

As long as each function returns a new value, I don't see where
lifetime of const enters in.  The return value of 'foo' will last at
least until it is used ('bar' is called'), even by the definition in
the ARM.

|> The third should probably be legal if anything is.

The third is definitly legal.  (A reference is an l-value.)
--
James Kanze                             email: kanze@us-es.sel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                   -- Beratung in industrieller Datenverarbeitung




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Fri, 17 Sep 1993 07:31:12 GMT
Raw View
In article <1993Sep12.161724.4012@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>In article <rfgCD2w04.Mn9@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
>>
>>I personally don't give a darn which notation we must use, as long as it
>>is one which is consistant with providing full lvalue-safety in the language.
>
> Ron: YOU proved this was impossible without totally
>unacceptable changes to the language...

Nonsense.  That is simply untrue.

I have noted (as have others) that the ability to call member functions on
class-type values (which are not lvalues) opens up an "lvalue safety" hole
in the language.  I have suggested simply closing that hole by saying that
such calls are illegal.

One other hole exists, and it can also easily be plugged (without too much
pain).  Specifically, expressions whose nominal types are reference-to-const
types normally decay into things which are lvalues.  If we simply said that
the decay products in such cases were *not* lvalues (and if we also made the
other change I have suggested above) then all of the (2) holes would be
plugged, and that language would be lvalue-safe (as C already is).

Here is an example which may shed some light on this additional rule change:

 extern const int & func();
 int *ip;

 void foobar ()
 {
  ip = & func(); // error!
 }

The expression `func()' nominally has type `const int &', but under normal
circumstances, its type "decays" to just type `int'.

Now, the resulting value (after the decay step) is itself an lvalue.  (At
least this seems to be the case if one consults existing implementations...
the ARM is entirely less than clear about this though.)

Given that the result, after decay, is an lvalue, it is valid (currently)
to apply the unary & operator to that (decayed) result value of the function
call.

What I am suggesting is that the decay product for any expression whose
type is something like `const T &' should no longer be considered to be
an lvalue.

This simple change would render the application of the unary & operator
(to such a decayed value) illegal, thereby closing the second of the two
lvalue-safety holes which are now present in the language.

These two simple language changes would make it impossible to ever obtain
the address of any (non-lvalue) "temporary" value.  That in turn would
help to prevent a number of subtle (but potentially common) programming
errors where temporaries are stored into just before they disappear
(a truly useless exercize) and/or where either the whole of some
temporary or some sub-part thereof has its address used at some point
AFTER the temporary itself has been destructed (a truly dangerous practice).

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 12 Sep 1993 16:17:24 GMT
Raw View
In article <rfgCD2w04.Mn9@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
>
>I personally don't give a darn which notation we must use, as long as it
>is one which is consistant with providing full lvalue-safety in the language.

 Ron: YOU proved this was impossible without totally
unacceptable changes to the language. In particular, there aint
no way to stop a programmer getting the address of a non-lvalue
of any kind with a few one line functions:

 template<class T> T const* address(T const& x) { return &x; }
 template<class T> T volatile* address(T volatile& x) { return &x; }
 template<class T> T* address(T& x) { return &x; }
 template<class T>
  T const volatile* address(T const volatile& x) { return &x; }
 //Aw, OK, that one is two lines :-)

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Thu, 9 Sep 1993 08:32:20 GMT
Raw View
In article <1993Aug27.050151.6722@mole-end.matawan.nj.us> mat@mole-end.matawan.nj.us writes:
>
>As far as I'm concerned, `lvalue safety' is one big red herring.

If you really believe that, then you must necessarily also believe that
the X3J11 (ANSI C) standardization committee wasted a lot of time (to
no good end) by carefully specifying what thing are (and what things are
not) lvalues in C.

(But perhaps you know something that none of them knew.)

Oh yes!  And you must also believe that when the definition of C++ was
changed such that only references to `const' qualified types could be
initialized with non-lvalue expression, that this was a useless change
(and a big waste of time) also.

In fact, you are mistaken on all of these counts.  The concept of lvalues
(and of lvalue safety) is a central part of what makes C a simple, robust,
efficient, implementable, and optimizable language.  Additionally, despite
what you may believe, there really *was* a good reason why the definition
of C++ was changed to disallow non-lvalues as initializers for references
to types which were NOT const qualified.  (And if you ask nicely, perhaps
Jerry Schwarz will tell you all about it.  It relates to making it MUCH
HARDER to make a number of really dumb programming mistakes... so in that
sense, lvalue-safety is much like type-safety.)

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Thu, 9 Sep 1993 08:38:27 GMT
Raw View
In article <KANZE.93Aug30200559@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
>In article <rfgCC9Anq.HJ@netcom.com> rfg@netcom.com (Ronald F.
>Guilmette) writes:
>
>|> In article <1993Aug16.133254.23424@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>|> >In article <rfgCBtH20.Ex8@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
>|> >>...
>
>|> >>  foo().bar()
>
>|> >>But is it really all that common?  I rather doubt it.
...
>I think that this is a common construct, even when not overloading
>operators.  I certainly use it a lot.  And I suspect that anyone from
>a function programming background would use it even more than I do.
>
>A typical example, from my string class, on string s:
>
> s.extract( 5 , 5 ).lTrim().rPad( 7 )
>
...
>Of course, extract, lTrim and rPad could be written as friends, with
>the source string as the first parameter.  In this case, the above
>example would read:
>
> rPad( lTrim( extract( s , 5 , 5 ) ) , 7 )
>
>I personally prefer the first notation; someone with a lot of lisp
>experience might prefer the second.

I personally don't give a darn which notation we must use, as long as it
is one which is consistant with providing full lvalue-safety in the language.
(Which is to say, I could easily live with only using the second notation
you have shown.)

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Thu, 9 Sep 1993 08:55:47 GMT
Raw View
In article <KANZE.93Aug30205847@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
>In article <1993Aug16.091459.151@mole-end.matawan.nj.us>
>mat@mole-end.matawan.nj.us writes:
>
>|> In article <rfgCBtH20.Ex8@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:
>
>|> > I think these facts are relevant in this case.  It is certainly true that
>|> > there must be *some* code out there (somewhere) which contains expressions
>|> > of the form:
>
>|> >   foo().bar()
>
>|> > But is it really all that common?  I rather doubt it.  Would people really
>|> > need to spend millions of man-hours and zillions of dollars to change all
>|> > of these (rare?) cases to look like:
>|> >
>|> >   TYPE temp = foo();
>|> >   temp.bar();
>
>That is fine if it is just one function being chained.  But what
>happens when it is two? three? more?  Each function call becomes an
>extra statement.

Big deal?  If this is the only rationale for keeping lvalue-unsafe features
in the language, then I'd say the proponents of lvalue-unsafety are on
pretty shakey ground.

Look, I never said that lvalue-safety wasn't going to cost anybody anything.
The question is "Is it worth the price?"  The price is very modest indeed,
and lvalue-safety can indeed act (like type-safety) as a form of insurance
against stupid programmer misteaks. ;-)

Do you buy insurance for your car?  For your house?  For your health?  Then
why not buy a little insurance against stupid programming mistakes?

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Thu, 9 Sep 1993 10:06:15 GMT
Raw View
In article <1993Sep1.210039.12720@brt.com> robert@brt.com (Robert Blumen) writes:
>James Kanze (kanze@us-es.sel.de) wrote:
>: In article <1993Aug16.091459.151@mole-end.matawan.nj.us>
>: mat@mole-end.matawan.nj.us writes:
>:
>: |> In article <rfgCBtH20.Ex8@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:
>:
>: |> > I think these facts are relevant in this case.  It is certainly true that
>: |> > there must be *some* code out there (somewhere) which contains expressions
>: |> > of the form:
>:
>: |> >   foo().bar()
>:
>: |> > But is it really all that common?  I rather doubt it.  Would people really
>: |> > need to spend millions of man-hours and zillions of dollars to change all
>: |> > of these (rare?) cases to look like:
>: |> >
>: |> >   TYPE temp = foo();
>: |> >   temp.bar();
>:
>: That is fine if it is just one function being chained.  But what
>: happens when it is two? three? more?  Each function call becomes an
>: extra statement.
>: --
>: James Kanze                             email: kanze@us-es.sel.de
...
>Such as, for example, using an ostream to output a series of
>variables.
...
>Robert Blumen    | Berkeley Research and Trading

Correct me if I'm wrong, but aren't most of those << and >> operators
*already* non-member functions??

If so, then your point is simply inapplicable.

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Tue, 31 Aug 1993 16:56:14 GMT
Raw View
In article <KANZE.93Aug30200559@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
>In article <rfgCC9Anq.HJ@netcom.com> rfg@netcom.com (Ronald F.
>Guilmette) writes:
>
>|> In article <1993Aug16.133254.23424@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>
>Of course, in the above example, all of the member functions (extract,
>lTrim and rPad) are 'const', so John's suggestion of only allowing
>const member functions to be called on temporaries would not cause any
>particular problem.  But with the lifetime of temporaries now well
>defined, something like the following becomes very useful:
>
> createWindow( WindowParameters()
>   .setScrollBar( WindowParameters::right )
>   .setBackgroundColor( WindowParameters::red ) ) ;
>
>'WindowParameters()' is an explicit call to the constructor, thus, a
>temporary.  In this case, the constructor initializes all of the
>parameters to their default values.  (There may easily be over a
>hundred parameters.  We don't want to have to specify them all.)  Each
>of the following functions changes *one* parameter, and returns a
>reference to the temporary object for the next function to use.  But
>this entails calling a non-const function on a temporary.

 It neednt:

 createWindow( ((WinParams&) WindowParameters())
   .setScrollBar( WindowParameters::right )
   .setBackgroundColor( WindowParameters::red ) ) ;

where I use an *explicit* cast to exactly the functionality you want:
calling a non-const function on a temporary. This also
happens to work for non-member functions:

 f(int& x);
 f( (int&) (1+2) );

The point is that the behaviour for non-member and member frunctions
should be equivalent to keep the language nice and simple.

Indeed the committee is currently considering the other alternative
to my 'non-lvalues are const' rule, namely, 'non-lvalues are non-const',
and allowing:

 f(int&);
 f(1+2); // fine, no cast required,
  // non-const ref may take a non-lvalue

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: robert@brt.com (Robert Blumen)
Date: Wed, 1 Sep 1993 21:00:39 GMT
Raw View
James Kanze (kanze@us-es.sel.de) wrote:
: In article <1993Aug16.091459.151@mole-end.matawan.nj.us>
: mat@mole-end.matawan.nj.us writes:
:
: |> In article <rfgCBtH20.Ex8@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:
:
: |> > I think these facts are relevant in this case.  It is certainly true that
: |> > there must be *some* code out there (somewhere) which contains expressions
: |> > of the form:
:
: |> >   foo().bar()
:
: |> > But is it really all that common?  I rather doubt it.  Would people really
: |> > need to spend millions of man-hours and zillions of dollars to change all
: |> > of these (rare?) cases to look like:
: |> >
: |> >   TYPE temp = foo();
: |> >   temp.bar();
:
: That is fine if it is just one function being chained.  But what
: happens when it is two? three? more?  Each function call becomes an
: extra statement.
: --
: James Kanze                             email: kanze@us-es.sel.de
: GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
: Conseils en informatique industrielle --
:                    -- Beratung in industrieller Datenverarbeitung

Such as, for example, using an ostream to output a series of
variables.

Robert


------------------------------------------------------------------------------
Robert Blumen    | Berkeley Research and Trading
Software Engineer   | 1600 Shattuck Ave
robert@brt.com    | Suite 222
(510) 548-4422    | Berkeley, CA 94709
------------------------------------------------------------------------------
--
------------------------------------------------------------------------------
Robert Blumen    | Berkeley Research and Trading
Software Engineer   | 1600 Shattuck Ave
robert@brt.com    | Suite 222




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Tue, 24 Aug 1993 09:07:01 GMT
Raw View
In article <1993Aug16.133254.23424@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>In article <rfgCBtH20.Ex8@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
>>...
>>>There is a lot of confusion here...
>>Amen.
...
> It makes sense to call const functions on non-lvalues:
>what harm is done?

Allowing this breaks what I have called "lvalue safety" (in discussions with
X3J16 members).  Basically, it allows you to take the address of something
which is a temporary (and which may disappear or be destructed at any
moment).  This is bad news.  It effectively opens up a crater in front
of each and every C++ programmer, and then entices him to jump into it.

Fortunately, this little land mine *could* be eliminated from the language
without too much difficulty.  One step towards this goal would be to make
it illegal to call a member function on a non-lvalue... i.e. on *any* non-
value.

>>  foo().bar()
>>
>>But is it really all that common?  I rather doubt it.
>
> I'd say it was common, but its true I'm guessing. Its just that
>the code looks like this:
>
> a + b + c;
>
>where + is a member operator. Perhaps this is bad style...

Yes.  It would be.  The very same thing *could* be done with overloaded
non-member operators, and you could thus avoid the pit of lvalue madness.

>>If it were made illegal to call a member function (*any* member function)
>>on a (non-reference-type) value returned from any function, this would
>>have the effect of cleaning up a gaping hole in language which currently
>>allows things that are essentially non-lvalues to *become* lvaues (and
>>to have their addresses taken, either explicitly or implicitly).
>>
>
> No, it would not clean up this problem. You would have
>to ban const references being initialised by non-lvalue to do that.

Not true.  You would however have to ban taking the address of any entity
designated by an reference-to-const type thing.  Thus, the following would
be disallowed:

  void example (int const & arg)
  {
      int *ip = &arg; // error
  }

These two modest restrictions, taken together, would restore to C++ the
same lvalue safety which we find in C++... and this would NOT just be
something to satisfy academics and pedants.  It would yield *real*
benefits in terms of more maintainable (and correct) code, and would
have the further advantage of making certain optimizations easier for
compilers to find and perform.

> template<class T>
> T const * address(T const & x) { return &x; }
> address(1+2); // address of anything :-)
>
>If you banned this, you couldn't write a copy constructor .. and C++
>would fall apart. It would lose expressions and become an assembler :-)

That is nonsense.  There is nothing about your example (or mine) that is
is irreplacable in the language.  What makes you think that it is either
necessary or vital to be able to take the address of things which are not
lvalues.  (ANSI C does perfectly well without such a misguided capability,
thank you.)
--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Tue, 24 Aug 1993 09:32:17 GMT
Raw View
In article <1993Aug16.091459.151@mole-end.matawan.nj.us> mat@mole-end.matawan.nj.us writes:
>In article <rfgCBtH20.Ex8@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:
>> In article <KANZE.93Aug3202045@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
>> ...
>> I think these facts are relevant in this case.  It is certainly true that
>> there must be *some* code out there (somewhere) which contains expressions
>> of the form:
>
>>   foo().bar()
>
>> But is it really all that common?  I rather doubt it.  Would people really
>> need to spend millions of man-hours and zillions of dollars to change all
>> of these (rare?) cases to look like:
>>
>>   TYPE temp = foo();
>>   temp.bar();
>
>But that has different semantics!

So what?

>It requires a copy constructor; the copy constructor might be unavailable.

Don't be silly Mark.  If the copy constructor were unavailable, then you
could hardly have returned a *copy* of the evaluated value of the expression
given in the return statement of `foo'.  In other words, if it was unavailable,
then we would not even be discussing this, because you couldn't even write
the function `foo'.

>It guarantees the existence of an object for the whole lifetime of the
>variable; this messes up some of the optimizations the numerical analysis
>people are hoping to get.

You have it backwards.  Making C++ lvalue-safe would actually open up new
optimization opportunities in a lot of cases.  The whole point of non-lvalues
(in C) is that these are the *results* of expressions which you can use,
but which you cannot possibly get the addresses of.  Thus, it is implicitly
known to all compilers that (in C) any non-lvalue may be held in a register
at any time.  This allows compilers to use registers for intermediate values
in many cases where they would not otherwise be able to.  If C++ were itself
lvalue-safe (as ANSI C already is) then similar sorts of no-brainer optimiza-
tions would appear more often in C++.

>It is also error-prone, requiring a definition for TYPE s.t. if the return
>type of foo() ever changes, the code breaks.

Sorry.  That one sounds like a red herring to me.  If you wrote your code
in a proper fashion to being with, then you would also have declared foo like:

  TYPE foo ();

so that if `TYPE' changes, all affected sites are compatibly changed in one
swell foop.

>And finally, it effectively outlaws the use of surrogates/proxies.  Enough
>users want these things or the things they bring that we can't do that.

Unless I am mistaken, all of this surrogate/proxy stuff was all just a set
of clever hacks to work around the fact that the semantics of C++ (when it
comes to calling const and/or non-const member functions on temporaries)
are a bit deranged.  If we dispose of the entire notion of calling member
functions on temporaries, then you can dispense entirely with all of this
surrogate/proxy rigamarole.

Or did you just want complexity for complexity's sake??

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: andrew@tagsys.com (Andrew Gideon)
Date: Wed, 25 Aug 1993 01:25:26 GMT
Raw View
In article 1M4@netcom.com, rfg@netcom.com (Ronald F. Guilmette) writes:
>In article <1993Aug16.091459.151@mole-end.matawan.nj.us> mat@mole-end.matawan.nj.us writes:

>
>>And finally, it effectively outlaws the use of surrogates/proxies.  Enough
>>users want these things or the things they bring that we can't do that.
>
>Unless I am mistaken, all of this surrogate/proxy stuff was all just a set
>of clever hacks to work around the fact that the semantics of C++ (when it
>comes to calling const and/or non-const member functions on temporaries)
>are a bit deranged.  If we dispose of the entire notion of calling member
>functions on temporaries, then you can dispense entirely with all of this
>surrogate/proxy rigamarole.
>

I wonder if this is true.  If I understand what you mean by "surrogate/proxy",
then I've used these in a couple of different ways.  I don't see how these
are working around the semantics associated with calling const/non-const
member functions on temporaries, but perhaps I'm just missing it.

The first way I've used surrogates (I've always just called these "helper
classes") is to provide different semantics between assigning TO something
and FROM something.  Typically this is used on T::operator[](T2).  This returns
a non-const object which has both methods operator T3() and operator=(T4).

The second way I've used these is to provide the appearance of function
overloading on return type.  In that case, the function returns an object
(const or non-const, depending upon the application) which supports methods
like operator T5(), operator T6(), etc.

We all seem to agree that these would not work if I could not call member
functions on returned objects.  What I don't see is how these are working
around the semantics associated with calling member functions on temporaries.


---
 -----------------------------------------------------------
| Andrew Gideon              |                              |
| Consultant                 |                              |
|                            |   TAG Systems inc.           |
| Tel: (201) 890-7189        |   D2-181 Long Hill Road      |
| Fax: (201) 890-1581        |   Little Falls, N.J., 07424  |
| andrew@tagsys.com          |                              |
 -----------------------------------------------------------







Author: mat@mole-end.matawan.nj.us
Date: Fri, 27 Aug 1993 05:01:51 GMT
Raw View
In article <rfgCC9Btu.1M4@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:
> In article <1993Aug16.091459.151@mole-end.matawan.nj.us> mat@mole-end.matawan.nj.us writes:

...

[To take up just one line of argument here]

> >And finally, it effectively outlaws the use of surrogates/proxies.  Enough
> >users want these things or the things they bring that we can't do that.

> Unless I am mistaken, all of this surrogate/proxy stuff was all just a set
> of clever hacks to work around the fact that the semantics of C++ (when it
> comes to calling const and/or non-const member functions on temporaries)
> are a bit deranged.  If we dispose of the entire notion of calling member
> functions on temporaries, then you can dispense entirely with all of this
> surrogate/proxy rigamarole.

I do think you are mistaken.  Surrogates/proxies are used to allow certain
semantics to be made available under a particular syntax.  Yes, if we
dispense with the ability to have them, you will no longer have to worry
about them--but lots more people will miss them.  It's true that if we
took away every road, we'd no longer need highway bridges; that statesman Y
doesn't like highway bridges is hardly a good reason to take away all the
roads!

> Or did you just want complexity for complexity's sake??

I want to be able to take complexity unto myself so that my users
need not see it.  Surrogates are one way to do this.

As far as I'm concerned, `lvalue safety' is one big red herring.
--
 (This man's opinions are his own.)
 From mole-end    Mark Terribile

 mat@mole-end.matawan.nj.us, Somewhere in Matawan, NJ




Author: kanze@us-es.sel.de (James Kanze)
Date: 30 Aug 93 20:05:59
Raw View
In article <rfgCC9Anq.HJ@netcom.com> rfg@netcom.com (Ronald F.
Guilmette) writes:

|> In article <1993Aug16.133254.23424@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
|> >In article <rfgCBtH20.Ex8@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
|> >>...

|> >>  foo().bar()

|> >>But is it really all that common?  I rather doubt it.

|> > I'd say it was common, but its true I'm guessing. Its just that
|> >the code looks like this:
|> >
|> > a + b + c;
|> >
|> >where + is a member operator. Perhaps this is bad style...

|> Yes.  It would be.  The very same thing *could* be done with overloaded
|> non-member operators, and you could thus avoid the pit of lvalue madness.

I think that this is a common construct, even when not overloading
operators.  I certainly use it a lot.  And I suspect that anyone from
a function programming background would use it even more than I do.

A typical example, from my string class, on string s:

 s.extract( 5 , 5 ).lTrim().rPad( 7 )

This extracts 5 characters from string s, starting at position 5,
trims leading white space, and pads the string with trailing white
space to a length of 7.  The resulting string is returned.  Note that
string s is not modified.  This is called chaining functions, I
believe.

(The string class here is taken as an example.  It is not my goal to
say that every string class should have trim and pad functions.)

Of course, extract, lTrim and rPad could be written as friends, with
the source string as the first parameter.  In this case, the above
example would read:

 rPad( lTrim( extract( s , 5 , 5 ) ) , 7 )

I personally prefer the first notation; someone with a lot of lisp
experience might prefer the second.

Of course, in the above example, all of the member functions (extract,
lTrim and rPad) are 'const', so John's suggestion of only allowing
const member functions to be called on temporaries would not cause any
particular problem.  But with the lifetime of temporaries now well
defined, something like the following becomes very useful:

 createWindow( WindowParameters()
   .setScrollBar( WindowParameters::right )
   .setBackgroundColor( WindowParameters::red ) ) ;

'WindowParameters()' is an explicit call to the constructor, thus, a
temporary.  In this case, the constructor initializes all of the
parameters to their default values.  (There may easily be over a
hundred parameters.  We don't want to have to specify them all.)  Each
of the following functions changes *one* parameter, and returns a
reference to the temporary object for the next function to use.  But
this entails calling a non-const function on a temporary.
--
James Kanze                             email: kanze@us-es.sel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                   -- Beratung in industrieller Datenverarbeitung




Author: kanze@us-es.sel.de (James Kanze)
Date: 30 Aug 93 20:58:47
Raw View
In article <1993Aug16.091459.151@mole-end.matawan.nj.us>
mat@mole-end.matawan.nj.us writes:

|> In article <rfgCBtH20.Ex8@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:

|> > I think these facts are relevant in this case.  It is certainly true that
|> > there must be *some* code out there (somewhere) which contains expressions
|> > of the form:

|> >   foo().bar()

|> > But is it really all that common?  I rather doubt it.  Would people really
|> > need to spend millions of man-hours and zillions of dollars to change all
|> > of these (rare?) cases to look like:
|> >
|> >   TYPE temp = foo();
|> >   temp.bar();

That is fine if it is just one function being chained.  But what
happens when it is two? three? more?  Each function call becomes an
extra statement.
--
James Kanze                             email: kanze@us-es.sel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                   -- Beratung in industrieller Datenverarbeitung




Author: jss@lucid.com (Jerry Schwarz)
Date: Wed, 18 Aug 93 02:09:52 GMT
Raw View
In article <1993Aug16.133254.23424@ucc.su.OZ.AU>, maxtal@physics.su.OZ.AU (John Max Skaller) writes:

In article <1993Aug16.133254.23424@ucc.su.OZ.AU>, maxtal@physics.su.OZ.AU (John Max Skaller) writes:
|>  I makes sense to call const functions on non-lvalues:
|> what harm is done? These functions take a complex value
|> and compute a component value: they are equivalent to
|> a call of a friend. (The lookup is different though).
|>
|>  The advantage of allowing it is you can use inheritance
|> and polymorphism, which dont work with friends.
|>

And what harm is done if its allowed to call non-const members?
Modifications may be made that will be ignored, but is there really
any harm in that?

The main "harm" that has been pointed out to me is that if I treat
a temporary as non-const, then I might make a mistake with regards
to the function I intend to call.  That is, I might write "f()=g()" when
I meant "f()==g()" or "cin>>f()" when I meant "cout<<f()".

I have some sympathy with this claim, but not enough to persuade
me that temporaries should always be regarded as const.


   -- Jerry Schwarz




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Thu, 19 Aug 1993 13:41:54 GMT
Raw View
In article <1993Aug18.020952.5250@lucid.com> jss@lucid.com (Jerry Schwarz) writes:
>In article <1993Aug16.133254.23424@ucc.su.OZ.AU>, maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>
>In article <1993Aug16.133254.23424@ucc.su.OZ.AU>, maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>|>  I makes sense to call const functions on non-lvalues:
>|> what harm is done? These functions take a complex value
>|> and compute a component value: they are equivalent to
>|> a call of a friend. (The lookup is different though).
>|>
>|>  The advantage of allowing it is you can use inheritance
>|> and polymorphism, which dont work with friends.
>|>
>
>And what harm is done if its allowed to call non-const members?
>Modifications may be made that will be ignored, but is there really
>any harm in that?

 Yes:

 1) you cant do it for ordinary functions--an inconsistency
    allow f(int&) to take a temp and I'd be happier

 2) if you do (1), you cant overload f(int&) and f(const int&)
    because both are callable

Personally, I'd be happy with this: I dont think overloading on
const makes any real sense: I think its a bad practice anyhow.
There should be exactly one function 'f' taking a single
'int' argument: chose bewteen f(int), f(const int&), f(int&)
(etc for volatile). But dont chose more than one: if you want
two functions, use 'g' for the name of the second one :-)

Overloading should discriminate between different types,
like sin(float) and sin(complex). Not between different
ways of accessing the same type.


--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: jimad@microsoft.com (Jim Adcock)
Date: 20 Aug 93 20:22:27 GMT
Raw View
In article <rfgCBtH20.Ex8@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
|In article <KANZE.93Aug3202045@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
|>It is clear, for example, that you can call a function on it.
|
|That is NOT AT ALL clear, and I believe that issue was a part of the
|genesis of this thread.

People have been doing so "from day one" without prior complaint.
Thus it "is" in the language.  The issue then is how to clean up
the language specification to match the practice.

|In general, James' statement is correct, but the root question is "May
|a non-const member function be called for a value returned by a function
|call?"  (One might also question whether or not it should be allowed to
|call even a const member function of such a returned value, but I have
|yet to hear anyone argue that that should NOT be allowed; so this additional
|question seems altogether less controversial than the question of calling
|non-const member function on returned values.)

If const functions can be called on return values, then how about if
return values be implicitly considered 'const'?  It doesn't make all
the problems go away, and it invalidates some code, but it seems to
come closer to specifying the intent of a 'value'.

|In the case of `matrix.diagonal() = 1', that expression would be perfectly
|valid if the member function `diagonal()' returned a reference to an int.

And it would be illegal if you returned a SmartRef instead.  Thus you
would be defeating the intent of all the effort put into ADT support,
operator support etc.

|What I've seen happen several times now in X3J16 is that opponents of
|these kinds of non-backward-compatible "cleanups" of obscure problems
|in the language definition will always step forward and claim that they
|already have a zillion lines of code which are TOTALLY dependent upon
|the questionable usage being considered for removal.  Of course no hard
|evidence is every brought forward to substantiate such claims (cuz our
|code is all propretary, so we can't show any of it to you... yea right).

This is a vacuous argument against people holding the position opposite
to yours, since you are equally able to generate such statistics from
code bases in your possession, and use the resulting statistics to back
your position.  Yet you have not done so.  Therefore, the lack of
statistics neither supports your position nor their position.  Further,
unless your opponents *primary* position is that the only reason they
don't want to make this change is because of existing code base, then
there is not much reason for them to take the time and effort to generate
the statistics.  Whereas you have compelling reason to generate the
statistics because if you show this functionality is almost never
been used, then you can argue pursuasively that this is needlessly
complicated functionality that might as well be removed from the language
because it isn't being used anyway.  So quit bitching about your opponents
and generate the numbers yourself.

|I think these facts are relevant in this case.  It is certainly true that
|there must be *some* code out there (somewhere) which contains expressions
|of the form:
|
|  foo().bar()
|
|But is it really all that common?  I rather doubt it.  Would people really
|need to spend millions of man-hours and zillions of dollars to change all
|of these (rare?) cases to look like:
|
|  TYPE temp = foo();
|  temp.bar();

How about cases with operators and/or cast operators/ constructors:

 mat[i][j] = 12;

where mat[i] returns a smart pointer?

 or p->foo();

where p is a smart pointer with overloaded operator->() returning
an object of class type which in turn has operator->()  ???

My point being, no matter how often people actually use these functionalities,
you are not proposing localized changes in the language, you are proposing
general changes in the language.  The rules for operator->() would have
to be changed, for example.

|If it were made illegal to call a member function (*any* member function)
|on a (non-reference-type) value returned from any function, this would
|have the effect of cleaning up a gaping hole in language which currently
|allows things that are essentially non-lvalues to *become* lvaues (and
|to have their addresses taken, either explicitly or implicitly).

There are clearly other ways that the "hole" could be filled without
requiring such sweeping changes.  As a strawman proposal, note that
things could remain as is, except that language could be added
restricting the usages permitted of addresses of return values and
for what duration [ie until the next sequence point].  This proposal
would have the weakness of being yet another source of unconstrained
errors in programs.  Yet such is hardly new to C/C++!


I agree that this is problem that needs to be cleaned up.  I agree
that as a starting position there needs to be a clear and clean
definition of what an object is.  I don't agree with your proposed
'solution.'  I'd hope there is some other more restricted change
possible.




Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Mon, 23 Aug 1993 06:38:54 GMT
Raw View
jimad@microsoft.com (Jim Adcock) writes:

>rfg@netcom.com (Ronald F. Guilmette) writes:
>|
>|In the case of `matrix.diagonal() = 1', that expression would be perfectly
>|valid if the member function `diagonal()' returned a reference to an int.
>
>And it would be illegal if you returned a SmartRef instead.  Thus you
>would be defeating the intent of all the effort put into ADT support,
>operator support etc.

Why would it be illegal? Why wouldn't SmartRef::operator=()
be a const member function?  (It doesn't modify the reference,
it modifies the object that the reference refers to.)

 template <class T>
 class SmartRef {
  ...
  const SmartRef& operator = (const T&) const;
            ^^^^^
  ...
 };

--
Fergus Henderson                     fjh@munta.cs.mu.OZ.AU




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Sun, 15 Aug 1993 20:03:36 GMT
Raw View
In article <KANZE.93Aug3202045@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
>In article <BGLENDEN.93Jul29112338@colobus.cv.nrao.edu>
>bglenden@colobus.cv.nrao.edu (Brian Glendenning) writes:
>
>|> In article <KANZE.93Jul29132400@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
...
>
>|>    |> Of course the user expects to be able to write:
>|>    |>  matrix.diagonal() *= 2.0;
>|>    |> however this generates an error.
>
>|>    Normal, 'matrix.diagonal()' is not an l-value, and you can only bind
>|>    an l-value to a non-const reference.
>
>|> Right, I understood that, and I have just read that the Munich meeting
>|> just clarified that the result of return by value is not an lvalue.
...
>There is a lot of confusion here, and the Munich resolution has only
>scratched the surface.

Amen.

> Basically: 1) the value result of a function
>call is *not* an l-value.  Which does not necessarily imply that it is
>not an object.

That is absolutely true.  We will not know if the result of calling a
function is an "object" or not until we have a crisp definition of what
"an object" is.

>It is clear, for example, that you can call a function on it.

That is NOT AT ALL clear, and I believe that issue was a part of the
genesis of this thread.

In general, James' statement is correct, but the root question is "May
a non-const member function be called for a value returned by a function
call?"  (One might also question whether or not it should be allowed to
call even a const member function of such a returned value, but I have
yet to hear anyone argue that that should NOT be allowed; so this additional
question seems altogether less controversial than the question of calling
non-const member function on returned values.)

>2) The rules concerning where an l-value is required apply to
>the built-in operators, but not necessarily to user supplied
>operators.  In particular, "matrix.diagonal() = 1" is definitly
>illegal...

This is not necessarily (and always) true either.  (You have to be VERY
careful when stating general rules relating to C++, because there are
many many exceptional cases.)

In the case of `matrix.diagonal() = 1', that expression would be perfectly
valid if the member function `diagonal()' returned a reference to an int.
(In all fairness however, I should note for the record that the original
example which James was commenting on DID NOT declare `diagnonal()' to
return a reference, so James' statement was 100% accurate with respect to
that one example.)

>Alternatively, why isn't it legal?  It was specifically discussed, and
>decided, that whatever was done, it was legal to call a member
>function on a temporary...

I think this was where X3J16 took a wrong turn.  This should have been
outlawed.  I do sympathize with X3J16's dilemma in this case however.
Most existing implementations (including cfront) have allowed such
usage since time began, and thus, any attempts to outlaw such usage at
this point would have been certain to generate howls from at least some
X3J16 members.

DIGRESSION MODE ON<

What I've seen happen several times now in X3J16 is that opponents of
these kinds of non-backward-compatible "cleanups" of obscure problems
in the language definition will always step forward and claim that they
already have a zillion lines of code which are TOTALLY dependent upon
the questionable usage being considered for removal.  Of course no hard
evidence is every brought forward to substantiate such claims (cuz our
code is all propretary, so we can't show any of it to you... yea right).

And you can always tell (from the mere fact that the "backward compatability
card" is being played) that those opposing such cleanups have run out
of solid technical reasons why the "language bug" in question ought to
be retained.  But nontheless, these "backward compatability" arguments
often tend to stick anyway (depending upon the "weight", within X3J16,
of the person making the argument).

DIGRESSION MODE OFF>

I think these facts are relevant in this case.  It is certainly true that
there must be *some* code out there (somewhere) which contains expressions
of the form:

  foo().bar()

But is it really all that common?  I rather doubt it.  Would people really
need to spend millions of man-hours and zillions of dollars to change all
of these (rare?) cases to look like:

  TYPE temp = foo();
  temp.bar();

especially if the compiler was pointing them to the exact source lines
where such trivial changes were required??  I rather doubt it.

If it were made illegal to call a member function (*any* member function)
on a (non-reference-type) value returned from any function, this would
have the effect of cleaning up a gaping hole in language which currently
allows things that are essentially non-lvalues to *become* lvaues (and
to have their addresses taken, either explicitly or implicitly).

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Mon, 16 Aug 1993 13:32:54 GMT
Raw View
In article <rfgCBtH20.Ex8@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
>...
>>There is a lot of confusion here, and the Munich resolution has only
>>scratched the surface.
>
>Amen.

 Actually, there were *several* resolutions at Munich,
which independently look minor. But taken together, they do much
more than scratch the surface. On the other hand, they dont penetrate
to the fundamental issues yet, but help prepare for that.

>
>> Basically: 1) the value result of a function
>>call is *not* an l-value.  Which does not necessarily imply that it is
>>not an object.
>
>That is absolutely true.  We will not know if the result of calling a
>function is an "object" or not until we have a crisp definition of what
>"an object" is.

 Exactly.
>
>>Alternatively, why isn't it legal?  It was specifically discussed, and
>>decided, that whatever was done, it was legal to call a member
>>function on a temporary...
>
>I think this was where X3J16 took a wrong turn.  This should have been
>outlawed.  I do sympathize with X3J16's dilemma in this case however.
>Most existing implementations (including cfront) have allowed such
>usage since time began, and thus, any attempts to outlaw such usage at
>this point would have been certain to generate howls from at least some
>X3J16 members.

 I makes sense to call const functions on non-lvalues:
what harm is done? These functions take a complex value
and compute a component value: they are equivalent to
a call of a friend. (The lookup is different though).

 The advantage of allowing it is you can use inheritance
and polymorphism, which dont work with friends.

>I think these facts are relevant in this case.  It is certainly true that
>there must be *some* code out there (somewhere) which contains expressions
>of the form:
>
>  foo().bar()
>
>But is it really all that common?  I rather doubt it.

 I'd say it was common, but its true I'm guessing. Its just that
the code looks like this:

 a + b + c;

where + is a member operator. Perhaps this is bad style (should
have been a friend).

>If it were made illegal to call a member function (*any* member function)
>on a (non-reference-type) value returned from any function, this would
>have the effect of cleaning up a gaping hole in language which currently
>allows things that are essentially non-lvalues to *become* lvaues (and
>to have their addresses taken, either explicitly or implicitly).
>

 No, it would not clean up this problem. You would have
to ban const references being initialised by non-lvalue to do that.

 template<class T>
 T const * address(T const & x) { return &x; }
 address(1+2); // address of anything :-)

If you banned this, you couldn't write a copy constructor .. and C++
would fall apart. It would lose expressions and become an assembler :-)

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: mat@mole-end.matawan.nj.us
Date: Mon, 16 Aug 1993 09:14:59 GMT
Raw View
In article <rfgCBtH20.Ex8@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:
> In article <KANZE.93Aug3202045@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
> ...
> >There is a lot of confusion here, and the Munich resolution has only
> >scratched the surface.
>
> Amen.
 ...
> I think these facts are relevant in this case.  It is certainly true that
> there must be *some* code out there (somewhere) which contains expressions
> of the form:

>   foo().bar()

> But is it really all that common?  I rather doubt it.  Would people really
> need to spend millions of man-hours and zillions of dollars to change all
> of these (rare?) cases to look like:
>
>   TYPE temp = foo();
>   temp.bar();

But that has different semantics!

It requires a copy constructor; the copy constructor might be unavailable.

It guarantees the existence of an object for the whole lifetime of the
variable; this messes up some of the optimizations the numerical analysis
people are hoping to get.

It is also error-prone, requiring a definition for TYPE s.t. if the return
type of foo() ever changes, the code breaks.

And finally, it effectively outlaws the use of surrogates/proxies.  Enough
users want these things or the things they bring that we can't do that.
--
 (This man's opinions are his own.)
 From mole-end    Mark Terribile

 mat@mole-end.matawan.nj.us, Somewhere in Matawan, NJ




Author: jensk@hpbbn.bbn.hp.com (Jens Kilian)
Date: Tue, 10 Aug 1993 16:51:28 GMT
Raw View
> You're right. Besides, there's no reason one shouldn't
> allow a memory leak either: after all, no one said that enough
> proxies would get created in a program to exhaust memory.

I can't believe I actually read this.  A *real* program will eventually run
long enough to exhaust its memory that way, and you don't even mention
fragmentation.

Not impressed,

 Jens.

PS: If it's worth doing, it's worth doing *right*.
--
Internet: jensk@hpbbn.bbn.hp.com | Phone: (0|+49)7031-14-4785 (TELNET 778-4785)
MausNet:  Jens Kilian @ BB       | Fax :  (0|+49)7031-14-2049
---------------------------------+---------------------------------------------
As the air to a bird, or the sea to a fish, so is contempt to the contemptible.




Author: kanze@us-es.sel.de (James Kanze)
Date: 11 Aug 93 18:56:10
Raw View
In article <JENSK.93Aug10095128@hpbbrn.bbn.hp.com>
jensk@hpbbn.bbn.hp.com (Jens Kilian) writes:

|> > You're right. Besides, there's no reason one shouldn't
|> > allow a memory leak either: after all, no one said that enough
|> > proxies would get created in a program to exhaust memory.

|> I can't believe I actually read this.  A *real* program will eventually run
|> long enough to exhaust its memory that way, and you don't even mention
|> fragmentation.

I believe John Skaller was being ironic.

On the other hand, what consitutes a memory leak.  I know of a lot of
applications that allocate memory in static variables, the first time
the variable is used, and never free it.  Is this a memory leak?

Would you object to a compiler that leaks ten bytes per statement
compiled?  While I wouldn't approve of doing something like that
intentionally, as an error, it is certainly not in the same category
as generating wrong code.  And what if 'grep' doesn't free the memory
in the parse tree of its regular expression?

I do embedded systems.  In my application, memory leaks are fatal
errors.  But I is always a tradeoff.  In the case of the compiler, it
probably wouldn't be worthwhile to invest the time needed to fix it.
And in the case of 'grep', I can even imagine someone intentionally
not bothering.
--
James Kanze                             email: kanze@us-es.sel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                   -- Beratung in industrieller Datenverarbeitung




Author: jbuck@forney.eecs.berkeley.edu (Joe Buck)
Date: 11 Aug 1993 18:34:32 GMT
Raw View
kanze@us-es.sel.de (James Kanze) writes:
>On the other hand, what consitutes a memory leak.  I know of a lot of
>applications that allocate memory in static variables, the first time
>the variable is used, and never free it.  Is this a memory leak?

This is an interesting question; there are at least two possible
definitions that can be used, corresponding to how you detect and
report them.

Definition 1: we report all heap objects that have not been freed at
the end of the program as leaks.  Advantages: this is very easy to do.
Disadvantages: you only find the leaks at the end of the program, and
you need to write code that frees everything at the end, even though
the time to do this freeing is, in a sense, wasted.

Definition 2: we report all heap objects that don't have any pointers
to them as leaks (basically, we use a garbage collector as a garbage
detector).  It's a bit harder to implement, but then I guess you could use
the Boehm garbage collector to do this.  Advantages: you can detect
leaks at any point in the program by running the "garbage detector".
Disadvantages: you program could still be growing and growing in memory
size as long as everything is connected together by pointers and
references.

I think that it is useful to be able to detect "leaks" according to either
definition.

>I do embedded systems.  In my application, memory leaks are fatal
>errors.  But I is always a tradeoff.  In the case of the compiler, it
>probably wouldn't be worthwhile to invest the time needed to fix it.

I think it's best to have tools that tell you where the leaks are (like
Purify, for example), and then you can use your best engineering judgment
as to whether to fix them or not.  Sometimes it's not worth it (e.g. the
leaks are proportional to, but less than, the size of the input, and
inputs larger than available memory are extremely unlikely).


--
Joe Buck jbuck@ohm.EECS.Berkeley.EDU




Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Thu, 12 Aug 1993 10:12:35 GMT
Raw View
jensk@hpbbn.bbn.hp.com (Jens Kilian) writes:

[John MAX Skaller writes:]

>> You're right. Besides, there's no reason one shouldn't
>> allow a memory leak either: after all, no one said that enough
>> proxies would get created in a program to exhaust memory.
>
>I can't believe I actually read this.  A *real* program will eventually run
>long enough to exhaust its memory that way, and you don't even mention
>fragmentation.
>
>Not impressed,
>
> Jens.
>
>PS: If it's worth doing, it's worth doing *right*.

Actually, I think that this may have been exactly the point that
John Skaller was (in a subtle way) trying to convey.
[At least, that was how I read it.]
In a sense Paul Lucas is right that you can use a finite number
of static proxies, and for some purposes that would be adequate.
But if you believe that it's worth doing *right*, not just
worth doing, then Paul Lucas' solution is not going to be
satisfactory.

--
Fergus Henderson                     fjh@munta.cs.mu.OZ.AU




Author: kanze@us-es.sel.de (James Kanze)
Date: 12 Aug 93 19:46:15
Raw View
In article <9322420.5765@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU
(Fergus James HENDERSON) writes:

|> jensk@hpbbn.bbn.hp.com (Jens Kilian) writes:

|> [John MAX Skaller writes:]

|> >> You're right. Besides, there's no reason one shouldn't
|> >> allow a memory leak either: after all, no one said that enough
|> >> proxies would get created in a program to exhaust memory.
|> >
|> >I can't believe I actually read this.  A *real* program will eventually run
|> >long enough to exhaust its memory that way, and you don't even mention
|> >fragmentation.
|> >
|> >Not impressed,
|> >
|> > Jens.
|> >
|> >PS: If it's worth doing, it's worth doing *right*.

|> Actually, I think that this may have been exactly the point that
|> John Skaller was (in a subtle way) trying to convey.
|> [At least, that was how I read it.]
|> In a sense Paul Lucas is right that you can use a finite number
|> of static proxies, and for some purposes that would be adequate.
|> But if you believe that it's worth doing *right*, not just
|> worth doing, then Paul Lucas' solution is not going to be
|> satisfactory.

I read John's posting in the same light as Fergus did, but I think
that some nuancing is needed.

I return my proxies by value, which I believe is the "right" solution
(in the sense above).  But in another sense, the "right" solution is
the one which makes the program work to specifications.  And if these
specifications include time constraints, and you are spending an
inordinate amount of time in constructing and destructing proxies,
then Paul's solution becomes the only "right" solution.

Note that correctly used, proxies are never needed beyond the end of a
statement.  And in a practical sense, there is a limit to how complex
a statement can be (line length, compiler stack size, etc.).  So
reserving a pool of 64 proxies is a good gamble.  And if it makes the
performance difference that allows the application to run on a PC, and
not require a Sun, you've won your bet.  (Note that with the prevailing
uncertainty concerning destruction of temporaries, Paul's solution may
actually be more sure than the "right" one.)

Of course, you would normally only resort to tricks like that after
profiling had shown that this *was* the bottleneck, or after having
problems with the lifetime of temporaries issue (unlikely in the case
of proxies, which are normally only used once).  But given that you
will probably get expression stack overflow from the compiler before
you need more than 64 proxies simultaneously, I wouldn't criticise
Paul for continuing to use this solution on later projects even before
doing profiling.  (I don't think I'd do it myself, but I think it is a
reasonable decision.)
--
James Kanze                             email: kanze@us-es.sel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                   -- Beratung in industrieller Datenverarbeitung




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 8 Aug 1993 13:07:56 GMT
Raw View
In article <KANZE.93Aug5171252@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
>|> >  public:
>|> >   FooProxy& operator[]( int );
>|> >   FooProxy const& operator[]( int ) const;
>|> >   // ...
>|> >  };
>|> >
>|> > What problem is being solved with two Proxies?
>
>|>  The very large memory leak in your code above?
>
>|> (How do those 'FooProxy' objects created by operator[]
>|> get deleted?)
>
>Why do they have to get deleted?  I see nowhere where Paul says that
>they were allocated on the heap.

 You're right. Besides, there's no reason one shouldn't
allow a memory leak either: after all, no one said that enough
proxies would get created in a program to exhaust memory.

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: kanze@us-es.sel.de (James Kanze)
Date: 3 Aug 93 20:20:45
Raw View
In article <BGLENDEN.93Jul29112338@colobus.cv.nrao.edu>
bglenden@colobus.cv.nrao.edu (Brian Glendenning) writes:

|> In article <KANZE.93Jul29132400@slsvhdt.us-es.sel.de> kanze@us-es.sel.de (James Kanze) writes:
|>    |> class Matrix {
|>    |> public:
|>    |>     Vector diagonal();
|>    |> };

|>    |> As well as some global functions,
|>    |> e.g. void operator *= (Vector &left, const double &right)

|>    |> Of course the user expects to be able to write:
|>    |>  matrix.diagonal() *= 2.0;
|>    |> however this generates an error.

|>    Normal, 'matrix.diagonal()' is not an l-value, and you can only bind
|>    an l-value to a non-const reference.

|> Right, I understood that, and I have just read that the Munich meeting
|> just clarified that the result of return by value is not an lvalue.
|> However the current CFront 3 compiler, as well as the ARM language
|> seems to imply (to me!) that the result of matrix.diagonal() is an
|> lvalue, certainly statements like "matrix.diagonal() = 1;", which use
|> the Vector's operator= are presently allowed.

There is a lot of confusion here, and the Munich resolution has only
scratched the surface.  Basically: 1) the value result of a function
call is *not* an l-value.  Which does not necessarily imply that it is
not an object.  It is clear, for example, that you can call a function
on it.  2) The rules concerning where an l-value is required apply to
the built-in operators, but not necessarily to user supplied
operators.  In particular, "matrix.diagonal() = 1" is definitly
illegal if you have not redefined operator=, but probably legal if you
have.

A curious consequence is that while 'matrix.diagonal() *= 2' is
definitly illegal with the global function operator*= presented above,
it will probably be legal if operator*= is changed to be a (non-const)
member function of Vector.

BTW, when I use the word 'probably' in the above, I really should say,
unknown at this instance.  I am expressing a certain point of view
concerning what the standard says and will say, which may or may not
be shared by a majority of members on the standardization committee.


And Scott Meyers writes:

|> Does this mean that the standard technique for differentiating between uses
|> of operator[] as an lvalue and an rvalue by having it return a proxy object
|> (which itself redefines operator=) can no longer be used?

No.  Although it would probably be a good idea if you declared
operator= in the proxy as a const function!

Traditionally, to do this right in the most general case, you need two
proxy classes, one for const objects and one for non-const.
Obviously, you would not overload the operator= for the const proxy.
In most cases, however, the non-const proxy will only consist of two
functions, one to overload operator=, and another to overload a cast
operator to whatever you want as an r-value.  In this case, you can
avoid the const proxy by having the const operator[] return directly
whatever the proxy would return in r-value cases.

|> It's not clear to me how that solves the problem.  Given this:

|>     class ArrayOfT {
|>     public:
|>       ConstProxyForT operator[](int index) const;
|>       ProxyForT      operator[](int index);
|>     };

|> We want this to be legal:

|>     ArrayOfT collection;
|>     collecton[3] = collection[5];

|> This is only legal if

|>                  (collection.operator[](3)).operator=(...)

|> is.  But if the return value of ArrayOfT::operator[] is not an lvalue
|> (because it is an object), how can that call be legal?

Alternatively, why isn't it legal?  It was specifically discussed, and
decided, that whatever was done, it was legal to call a member
function on a temporary.  In this case, operator= is a member
function.

There is still active (and hot) discussion on whether it will be legal
to call all functions, or just const functions, on a non-l-value
object.  So to be sure, I would declare operator= to be a const
function.  This is actually logical, since it *doesn't* modify
ProxyForT in any way.

|> One odd thought occurs to me, but I'm not sure it's legal under the new
|> rules.  If ProxyForT::operator= is a *const* member function, then it might
|> be legal to invoke assignment on a temporary proxy object (because we'd be
|> binding the this pointer to a pointer-to-const, which is analogous to
|> binding a temporary to a reference-to-const).  However, to prevent
|> assignment to ConstProxyForT objects, it would be important to make
|> operator= in that class a *non-const* member function.  That is, the
|> assignment operator for non-const proxies would have to be const and the
|> assignment operator for const proxies would have to be non-const.  This is
|> highly unintuitive, but it makes no less sense than lots of other oddities
|> in C++.

You could just declare ConstProxyForT::operator= private, and not
implement it at all.
--
James Kanze                             email: kanze@us-es.sel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                   -- Beratung in industrieller Datenverarbeitung




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Wed, 4 Aug 1993 03:23:32 GMT
Raw View
In article <CB4uny.8Kt@cbnewse.cb.att.com> grumpy@cbnewse.cb.att.com (Paul J Lucas) writes:

> I missed something: Why do you need to flavors of Proxy?  I
> typically have:
>
>  class FooProxy {
>   // ...
>  public:
>   FooProxy& operator=( ... );
>   // ...
>  };
>
>  class Foo {
>   // ...
>  public:
>   FooProxy& operator[]( int );
>   FooProxy const& operator[]( int ) const;
>   // ...
>  };
>
> What problem is being solved with two Proxies?

 The very large memory leak in your code above?

(How do those 'FooProxy' objects created by operator[]
get deleted?)

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: kanze@us-es.sel.de (James Kanze)
Date: 5 Aug 93 17:12:52
Raw View
In article <1993Aug4.032332.13603@ucc.su.OZ.AU>
maxtal@physics.su.OZ.AU (John Max Skaller) writes:

|> In article <CB4uny.8Kt@cbnewse.cb.att.com> grumpy@cbnewse.cb.att.com (Paul J Lucas) writes:

|> > I missed something: Why do you need to flavors of Proxy?  I
|> > typically have:
|> >
|> >  class FooProxy {
|> >   // ...
|> >  public:
|> >   FooProxy& operator=( ... );
|> >   // ...
|> >  };
|> >
|> >  class Foo {
|> >   // ...
|> >  public:
|> >   FooProxy& operator[]( int );
|> >   FooProxy const& operator[]( int ) const;
|> >   // ...
|> >  };
|> >
|> > What problem is being solved with two Proxies?

|>  The very large memory leak in your code above?

|> (How do those 'FooProxy' objects created by operator[]
|> get deleted?)

Why do they have to get deleted?  I see nowhere where Paul says that
they were allocated on the heap.  In fact, I seem to recall having
seen a posting from him once where he explicitly said that he has a
pool of them that are returned in a round-robin fashion.  (Note that
FooProxy has no destructor.)  Of course, if you need more proxies that
are in the pool, strange things will happen.  But if the pool is large
enough...  Can you invent an example of code where you would need more
than 32 proxies (at once)?  What is the probability that such an
expression will actually occur in real code?  (And on the down side,
in the unlike event that it does occur, how much time do you think it
will take a naive user to find the bug?)
--
James Kanze                             email: kanze@us-es.sel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                   -- Beratung in industrieller Datenverarbeitung




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 1 Aug 1993 19:40:56 GMT
Raw View
In article <1993Jul29.180001.20848@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
>In article <BGLENDEN.93Jul29112338@colobus.cv.nrao.edu> bglenden@colobus.cv.nrao.edu (Brian Glendenning) writes:
>|                               I have just read that the Munich meeting
>| just clarified that the result of return by value is not an lvalue.
>
>Does this mean that the standard technique for differentiating between uses
>of operator[] as an lvalue and an rvalue by having it return a proxy object
>(which itself redefines operator=) can no longer be used?
>

 No, it doesnt mean that. It is an open issue and
currently being hotly debated whether non-values can bind
to non-const member functions.

 No matter what the resolution, it is reasonably
certain that a non-lvalue will continue to be able to be bound
to a *const* member function.

 So if you write the operator= for the proxy object
as const, you wont have any problem no matter what the
committee decides.

 If you want to have two proxies: const and non-const
ones, I advise that you use two classes:

 Proxy ...
 ConstProxy

which works even for global functions.

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,      CSERVE:10236.1703
        6 MacKay St ASHFIELD,     Mem: SA IT/9/22,SC22/WG21
        NSW 2131, AUSTRALIA




Author: grumpy@cbnewse.cb.att.com (Paul J Lucas)
Date: Mon, 2 Aug 1993 12:57:32 GMT
Raw View