Topic: Named return value optimisation- reprise.


Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/28
Raw View
On 23 Jun 97 23:25:57 GMT, Valentin Bonnard <bonnardv@pratique.fr>
wrote:
 >...
 >> 15Whenever an automatic (_basic.stc.auto_) class object is copied and the
 >>   original object and the copy have the same type, and the block containing
 >
 >                     Don't you prefer cv-qualified type ?

Yes, for consistency the (N)RVO should probably be applicable
regardless of cv-qualifiers otherwise subtle unexpected code changes
may occur if a function's return type later has a const qualifier
added.

 >
 >>   the original object exits (_stmt.jump_) immediately after the copy is
 >>   created, an implementation is permitted to treat the original and the
 >>   copy as two different ways of referring to the same object and not
 >>   perform a copy at all.  In that case, the object is destroyed when the
 >>   copy would have been destroyed without the optimization.
 >
 >Very good !
 >
 >An example could be added:
 >
 >// T as accessible an copy constructor and destructor, but we know
 >// nothing about their implementations at this point
 >T   foo (T x)
 >{
 >    T local = x;
 >    return local;
 >}
 >
 >void    bar ()
 >{
 >    foo (z);
 >}
 >
 >Here the copies 'local = x' or 'x = z' can't be elided
 >even if local or x aren't used again after the copy because...
 >but wait !
 >
 >The copy 'x = z' can't be elided because it appens before the
 >function call (of T foo (T)); what about 'local = x' ?
 >
 >The block containing the original object exits (_stmt.jump_)
 >immediately after the copy is created if the copy is optimised !
 >
 >Can an optimisation make annother one possible ?
 >
 >So the most optimised version without inlining is:
 >
 >T   foo (T& x) return (x) // g++ syntax
 >{
 >    return;
 >}
 >
 >void    bar ()
 >{
 >    new (&x) T (z);
 >    foo (x);
 >}
 >
 >With inlining bar () would become a NOP. [Is it the intent ?]
 >
 >> I think this only allows the RVO.
 >
 >T    global;
 >
 >void    foobar ()
 >{
 >    T xx = global;
 >}
 >
 >Here xx can be elided. [Is it the intent too ?]

I now think that the above rewording of 12.8/15 in terms of block
exiting is still too broad and can lead to the issues discussed above
(and it is also too restrictive in that it disallows the elision of
temps produced by a conversion operator during a function call.)

If it is desired to make the (N)RVO etc. standard-sanctioned
optimisations, then a better wording of 12.8/15 may be to specify that
copies via a copy constructor of (automatic) lvalues can only be
elided on function return, and copies of rvalues via a copy
constructor can be elided anywhere.
(see my other posts in this thread for the full wording).

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/30
Raw View
On 27 Jun 1997 10:19:10 PDT, Valentin Bonnard <bonnardv@pratique.fr>
wrote:

>Brian Parker <bparker@gil.com.au> writes:
>
>> I was unclear in my original posting- what I meant to say was that the
>> internal dynamically-allocated memory of local objects could be
>> retained by a GC, not that the automatic object itself was affected by
>> the garbage collector (also note that I am not particularly defending
>> that as a good design- I'm just pointing out that that it can happen).
>
>I don't understand how it is supposed to work:
>
>int* p;
>int t[] = {1, 2, 3};
>{
>    gc_vector<int> v (t, t+sizeof t/sizeof (int));
>    v.pushback (4);
>    p = &v[1];
>} // now p points to an element with value 2 in an unnamed array
>
>p = 0; // how the elements are destructed; in particular, I would
>// guess that v.end_of_storage = v.finish + 2 (for * 2 reallocation).
>// how is it possible to destroy elements in [start, finish) but not
>// in [finish, end_of_storage) ?

Good point- STL's standard allocator probably wouldn't interact well
with a simple garbage collector (in particular, for classes with
non-trivial destructors), though one could conceivably write a custom
gc allocator that would work in the example.
 A vector class  that directly allocated memory and didn't use a
separate allocator would be a better example to use in my original
post.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Mike Ferrel <mferrel@sr.hp.com>
Date: 1997/06/23
Raw View
Brian Parker wrote:
>
> I wonder if a better rewording of 12.8/15 would be to simply add a
> sentence preventing its use on pass-by-value.
>
> i.e. change 12.8/15 to -
>
> "Whenever a class object [intro.object] is copied via a *copy
> constructor* and the original object and the copy have the same type,
> *except on pass-by-value (5.2.2 Function call)* if the implementation
> can prove ... (rest of wording the same)."
>
> And remove the current examples, replacing them with a NRVO example.
>
> This would still allow copies to be elided on NRVO, RVO and
> initialisation of variables.
>
I have been following this thread with some bemusement, and can't
understand how a compiler could possibly replace a call-by-value with a
call by reference. The function body would have to contain entirely
different code.  For this to be possible, the compiler would have to
generate different function bodies containing code to accept value
parameters
as value parameters, and as reference parameters.  For functions with
more
than one value parameter, the exponential increase in numbers of
distinct function bodies would cause impossible code bloat.

The only way I see the compiler could possibly reasonably alter call
semantics is if the function were declared static, and its definition
occured after all calls, and the compiler had determined that the
optimisation could be used in all calls.  It seems like that's an awful
lot of compiler housekeeping for an exceedingly rare occurence.  I can't
imagine any implementer going to the effort.

Mike Ferrel
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/23
Raw View
On 20 Jun 1997 10:19:52 PDT, Jason Merrill <jason@cygnus.com> wrote:
>...
>15Whenever a class object is copied and the original object and the copy
>  have the same type, and the lifetime of the original object ends
>  immediately* after the copy is created, an implementation is permitted to
>  treat the original and the copy as two different ways of referring to the
>  same object and not perform a copy at all.  In that case, the object is
>  destroyed when the copy would have been destroyed without the
>  optimization.
>
>* For the purposes of this rule, implicit destructor calls are not
>  considered in the lifetime of an object.

On further consideration it has occurred to me that the elision of a
temporary created as a result of a conversion operator on a function
call would not be allowed by this wording of 12.8/15, as after the
temporary is copied there is an entire function call before the temp
is destructed at the end of the full-expression.

In every case except for the NRVO, the copy being elided is a copy of
a newly-constructed class temporary (rvalue). So perhaps rather than
attempting to have one simple rule that includes the NRVO, it may be
better to treat it as a special case i.e.

"
12.8/15
In a copy-initialisation (8.5/12), if the source type is identical to
the destination type and is a class rvalue, or in a function return
(6.63) is an lvalue of automatic storage duration, then the copy can
be elided and the source object constructed directly in the
destination's storage.
(Note: *this in a member function of a class temporary is an lvalue
expression and hence this paragraph does not apply when copying it.)"

I put the note about *this to make it clear that copies of *this in a
member function are (correctly) not elided even if "this" is an
rvalue.

Any comments on this wording?

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Barry Margolin <barmar@bbnplanet.com>
Date: 1997/06/23
Raw View
In article <33a885f3.6004413@news.ipswich.gil.com.au>,
Brian Parker <bparker@gil.com.au> wrote:
>Actually, it's not even absolutely clear in the case of objects about
>to go out of scope- in a garbage-collected implementation memory can
>be retained beyond the local scope (or, as in the MV++ example, some
>of the object's memory may be statically allocated).

Even in a GC'ed implementation, you can't refer to automatic objects once
the block they were allocated in has exited.  GC permits an implementation
to reclaim memory once it has determined that it can't be accessed, but
doesn't require it to retain memory after an object has been declared
inaccessible (either by going out of scope or by an explicit call to free()
or delete).  While it's possible for an implementation to make the explicit
action no-ops, and use GC for all reclamation (a la Lisp), as far as the
language semantics are concerned this lazy reclamation doesn't exist.

--
Barry Margolin, barmar@bbnplanet.com
BBN Corporation, Cambridge, MA
Support the anti-spam movement; see <http://www.cauce.org/>
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1997/06/23
Raw View
Jason Merrill <jason@cygnus.com> writes:

> In this testcase, the problem occurs because even though 'v' is not
> directly used after the call, references to data it controls are.  This
> would not be a problem if 'v' went out of scope immediately after the
> call, because any references to its data are undefined after it goes out of
> scope.  Thus, I suggest that 12.8/15 should be reworded to read

Correct.

> 15Whenever an automatic (_basic.stc.auto_) class object is copied and the
>   original object and the copy have the same type, and the block containing

                     Don't you prefer cv-qualified type ?

>   the original object exits (_stmt.jump_) immediately after the copy is
>   created, an implementation is permitted to treat the original and the
>   copy as two different ways of referring to the same object and not
>   perform a copy at all.  In that case, the object is destroyed when the
>   copy would have been destroyed without the optimization.

Very good !

An example could be added:

// T as accessible an copy constructor and destructor, but we know
// nothing about their implementations at this point
T   foo (T x)
{
    T local = x;
    return local;
}

void    bar ()
{
    foo (z);
}

Here the copies 'local = x' or 'x = z' can't be elided
even if local or x aren't used again after the copy because...
but wait !

The copy 'x = z' can't be elided because it appens before the
function call (of T foo (T)); what about 'local = x' ?

The block containing the original object exits (_stmt.jump_)
immediately after the copy is created if the copy is optimised !

Can an optimisation make annother one possible ?

So the most optimised version without inlining is:

T   foo (T& x) return (x) // g++ syntax
{
    return;
}

void    bar ()
{
    new (&x) T (z);
    foo (x);
}

With inlining bar () would become a NOP. [Is it the intent ?]

> I think this only allows the RVO.

T    global;

void    foobar ()
{
    T xx = global;
}

Here xx can be elided. [Is it the intent too ?]

--

Valentin Bonnard
mailto:bonnardv@pratique.fr
http://www.pratique.fr/~bonnardv (Informations sur le C++ en Francais)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1997/06/23
Raw View
John Potter <jpotter@falcon.lhup.edu> writes:

> It is also simple to disable by doing what many people recommend
> anyway and make the return a Thing const.  Then the types are not the
> same.

I don't think they are (at least it isn't IMO the intent).

--

Valentin Bonnard
mailto:bonnardv@pratique.fr
http://www.pratique.fr/~bonnardv (Informations sur le C++ en Francais)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/24
Raw View
On 23 Jun 1997 13:42:59 PDT, Barry Margolin <barmar@bbnplanet.com>
wrote:

>In article <33a885f3.6004413@news.ipswich.gil.com.au>,
>Brian Parker <bparker@gil.com.au> wrote:
>>Actually, it's not even absolutely clear in the case of objects about
>>to go out of scope- in a garbage-collected implementation memory can
>>be retained beyond the local scope (or, as in the MV++ example, some
>>of the object's memory may be statically allocated).
>
>Even in a GC'ed implementation, you can't refer to automatic objects once
>the block they were allocated in has exited.  GC permits an implementation
>to reclaim memory once it has determined that it can't be accessed, but
>doesn't require it to retain memory after an object has been declared
>inaccessible (either by going out of scope or by an explicit call to free()
>or delete).  While it's possible for an implementation to make the explicit
>action no-ops, and use GC for all reclamation (a la Lisp), as far as the
>language semantics are concerned this lazy reclamation doesn't exist.

I was unclear in my original posting- what I meant to say was that the
internal dynamically-allocated memory of local objects could be
retained by a GC, not that the automatic object itself was affected by
the garbage collector (also note that I am not particularly defending
that as a good design- I'm just pointing out that that it can happen).

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/24
Raw View
On 23 Jun 1997 13:42:42 PDT, Mike Ferrel <mferrel@sr.hp.com> wrote:
>...
>I have been following this thread with some bemusement, and can't
>understand how a compiler could possibly replace a call-by-value with a
>call by reference. The function body would have to contain entirely
>different code.  For this to be possible, the compiler would have to
>generate different function bodies containing code to accept value
>parameters
>as value parameters, and as reference parameters.  For functions with
>more
>than one value parameter, the exponential increase in numbers of
>distinct function bodies would cause impossible code bloat.
>
>The only way I see the compiler could possibly reasonably alter call
>semantics is if the function were declared static, and its definition
>occured after all calls, and the compiler had determined that the
>optimisation could be used in all calls.  It seems like that's an awful
>lot of compiler housekeeping for an exceedingly rare occurence.  I can't
>imagine any implementer going to the effort.

Using an inlined function could result in the optimisation being
applied differently in different compilation units.

The problem is that even if the optimisation was only applied rarely,
just the possibility of its application means that one must code as if
it will be applied.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/24
Raw View
On 20 Jun 97 16:53:12 GMT, "Bill Wade" <bill.wade@stoner.com> wrote:

>Brian Parker <bparker@gil.com.au> wrote in article
>> [deleted]
>> I wonder if a better rewording of 12.8/15 would be to simply add a
>> sentence preventing its use on pass-by-value.
>>
>> i.e. change 12.8/15 to -
>>
>> "Whenever a class object [intro.object] is copied via a *copy
>> constructor* and the original object and the copy have the same type,
>> *except on pass-by-value (5.2.2 Function call)* if the implementation
>> can prove ... (rest of wording the same)."
>
[example demonstrating failure of rewording snipped]

>
>It seems that this code is "reasonable" in the absence of the alias
>optimization.  However it doesn't work in the presence of the optimization.
> I don't think changing wording to "function call" helps.

Yes, my rewording above was flawed- I now think a better rewording
would be to allow the copy to be elided when copying rvalues of the
same type, and lvalues only on function return (to allow the NRVO).

(full wording is in another post in this thread)

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "Bradd W. Szonye" <bradds@concentric.net>
Date: 1997/06/25
Raw View
Brian Parker <bparker@gil.com.au> wrote in article
<33aa7505.42328922@news.ipswich.gil.com.au>...
>
> This is actually a very important issue with respect to the
> ramifications of the rewriting or deleting of 12.8/15- would deleting
> 12.8/15 still allow temporary elision on function return,
> initialisation and conversions on function calls?
> I think not, and hence if 12.8/15 is rewritten it will need to
> explicitly allow for these cases if desired.

You've finally won me over to your side (except that I'm not worried about
NRVO). Allowing elision of copies in general is a bad thing; there are too
many places where it could go wrong. Instead, it is best to explicitly list
the acceptable temporary elisions:

* In creating the temporary passed by 'return' the implementation may elide
the calling of the copy constructor if the value returned is a
compiler-generated temporary (RVO) or an automatic variable declared in the
scope of the function (NRVO).

* During initialization (of variables, function arguments, etc.), the
implementation may elide compiler-generated temporaries.

On the second point: this allows removal of duplicate "implicit conversion"
temporaries created during function call and variable initialization and of
the extra copy in "assignment-style" initialization, but not of named
"temporaries" like formal parameters to functions.

I think this covers all of the "usual" temporary elisions in C++. What it
does not allow is lifetime analysis to find "dead" objects and reuse their
storage. That's still possible (by the as-if rule) for built-ins and
classes with no user-defined copy constructors or destructors in their
ancestry. While it would be nice to allow lifetime merging, it's really
only practical for objects with totally mechanical, value-like copy
semantics. Any more than that, and you quickly run into a situation where
the compiler and programmer are at odds about what the programmer meant.
--
Bradd W. Szonye
bradds@concentric.net
http://www.concentric.net/~Bradds
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1997/06/27
Raw View
Brian Parker <bparker@gil.com.au> writes:

> I was unclear in my original posting- what I meant to say was that the
> internal dynamically-allocated memory of local objects could be
> retained by a GC, not that the automatic object itself was affected by
> the garbage collector (also note that I am not particularly defending
> that as a good design- I'm just pointing out that that it can happen).

I don't understand how it is supposed to work:

int* p;
int t[] = {1, 2, 3};
{
    gc_vector<int> v (t, t+sizeof t/sizeof (int));
    v.pushback (4);
    p = &v[1];
} // now p points to an element with value 2 in an unnamed array

p = 0; // how the elements are destructed; in particular, I would
// guess that v.end_of_storage = v.finish + 2 (for * 2 reallocation).
// how is it possible to destroy elements in [start, finish) but not
// in [finish, end_of_storage) ?

--

Valentin Bonnard
mailto:bonnardv@pratique.fr
http://www.pratique.fr/~bonnardv (Informations sur le C++ en Francais)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/27
Raw View
On 25 Jun 97 04:24:00 GMT, "Bradd W. Szonye" <bradds@concentric.net>
wrote:
>...
>* In creating the temporary passed by 'return' the implementation may elide
>the calling of the copy constructor if the value returned is a
>compiler-generated temporary (RVO) or an automatic variable declared in the
>scope of the function (NRVO).
>
>* During initialization (of variables, function arguments, etc.), the
>implementation may elide compiler-generated temporaries.
>
>On the second point: this allows removal of duplicate "implicit conversion"
>temporaries created during function call and variable initialization and of
>the extra copy in "assignment-style" initialization, but not of named
>"temporaries" like formal parameters to functions.
>
>I think this covers all of the "usual" temporary elisions in C++. What it
>does not allow is lifetime analysis to find "dead" objects and reuse their
>storage. That's still possible (by the as-if rule) for built-ins and
>classes with no user-defined copy constructors or destructors in their
>ancestry.
> While it would be nice to allow lifetime merging, it's really
>only practical for objects with totally mechanical, value-like copy
>semantics. Any more than that, and you quickly run into a situation where
>the compiler and programmer are at odds about what the programmer meant.

Yes, if it is decided to make provision for the usual temporary
elisions in the standard, then 12.8/15 should explicitly specify the
cases where the temps can be elided.

I think that the following wording limits temp elision to these cases-

"12.8/15:
In a copy-initialisation (8.5/12), if the source type is identical to
the destination type and is a class rvalue, or in a function return
(6.63) is an lvalue of automatic storage duration, then the copy can
be elided and the source object constructed directly in the
destination's storage."

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Jason Merrill <jason@cygnus.com>
Date: 1997/06/18
Raw View
>>>>> Brian Parker <bparker@gil.com.au> writes:

> Trouble is, the STL also plays aliasing games. In fact, many classes
> using dynamically allocated memory would fall prey to the 12.8/5 bugs.
>  I repeat here a previously posted example using STL (it involves
> 12.8/5 being used to elide the copy on pass-by-value).

In this testcase, the problem occurs because even though 'v' is not
directly used after the call, references to data it controls are.  This
would not be a problem if 'v' went out of scope immediately after the
call, because any references to its data are undefined after it goes out of
scope.  Thus, I suggest that 12.8/15 should be reworded to read

15Whenever an automatic (_basic.stc.auto_) class object is copied and the
  original object and the copy have the same type, and the block containing
  the original object exits (_stmt.jump_) immediately after the copy is
  created, an implementation is permitted to treat the original and the
  copy as two different ways of referring to the same object and not
  perform a copy at all.  In that case, the object is destroyed when the
  copy would have been destroyed without the optimization.

I think this only allows the RVO.

Jason
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Barry Margolin <barmar@bbnplanet.com>
Date: 1997/06/18
Raw View
In article <33a5f2fc.3455132@news.ipswich.gil.com.au>,
Brian Parker <bparker@gil.com.au> wrote:
>As far as I can see, 12.8/15 could only be unambiguously safely
>applied by a compiler for a class with bitwise copy semantics, i.e.
>either the class and all its members and base classes have implicitly
>defined copy constructors, or any user-provided explicit copy
>constructors must involve only simple assignment of members. In these
>cases, though, the copy could already be elided by the "as if" rule.

I suspect this is the optimization that this section is trying to allow.
However, the as-if rule is not sufficient to permit it, because the as-if
rule is violated if the class or any of its members or bases have side
effects in their destructors (as in the destructor_count++ example that
someone posted).

I think that NRVO should be reworded to specify that if the copy
constructor has bitwise copy semantics, you can't depend on destructors
being run during an assignment.

--
Barry Margolin, barmar@bbnplanet.com
BBN Corporation, Cambridge, MA
Support the anti-spam movement; see <http://www.cauce.org/>
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: fjh@mundook.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/18
Raw View
Pete Becker <petebecker@acm.org> writes:

>Fergus Henderson wrote:
>>
> You know, after I posted this I realized it was too clever. Making
>destructor_count a global variable makes it clearer. I'm pretty sure
>that I think static data is not part of an object, but that's an
>unnecessary complication here.

Well, my point is that it is very difficult to draw the line.
What _is_ "the data" associated with an object?
Is it just the sub-objects (members and base classes) of the object?
Is it all the memory in the program?  Or is it somewhere in between?
There are a lot of different shades of gray:

 - objects whose data is contained only in their sub-objects
 - objects that use pointers allocated with `new'
 - objects that use pointers allocated from a private memory pool,
  where the private memory pool is static data
 - objects that allocate indices into a static array
 - objects that allocate bits from a static bit-vector
 - objects that increment or decrement a static counter

> The real point of all this is this:
[... example elided ...]
> I don't see any fundamental difference between these two classes,
>merely a difference in implementation. A definition of "object" that
>says they have different properties is wrong. Internally they are

Coming up with standardese to say exactly which data is considered part
of an object seems to me to be a very difficult task.  Unless you say
"all of the data in the program", then there will always be a pair of
examples that are conceptually very similar, but for which the data is
considered part of the object in one example but not the other.

>Internally they are
>somewhat different, but from the outside they present exactly the same
>interface and exactly the same states and behaviors.

Nope, your two example classes _are_ different from the outside:

 const C2 c2;
 const_cast<C2&>(c2).set_data(42); // fine

 const C1 c1;
 const_cast<C1&>(c1).set_data(42); // undefined behaviour

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: fjh@mundook.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/18
Raw View
boukanov@sentef1.fi.uib.no (Igor Boukanov) writes:

>> vector<int> reverse_vec(vector<int> v)
>> {
>>         // v passed by-value, so we can safely modify it in-place
>>         reverse(v.begin(), v.end());
>>         return v;
>> }
>
>As I undestand the current rule the argument for reverse_vec should be copied
>because it is used when "reverse(v.begin(), v.end())" is called.

The formal parameter `v' is used again, but the actual argument passed in by
the caller might not be used again.  The rule allows compilers to optimize
away copies if _either_ the source or the target of the copy is not
used again.

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Jason Merrill <jason@cygnus.com>
Date: 1997/06/18
Raw View
>>>>> Mattias Lundstroem <ehsmalu@aom.ericsson.se> writes:

> Jason Merrill wrote:

>> But I'm OK with that.  I'm comfortable saying that playing games with
>> references and values in the same class, and relying on the copy
>> constructor to switch between them, is asking for trouble.

> My understanding is that the rule could break code like
> that supplied by Brian Parker(below). Did anyone refute this?

[STL "reverse_vec" example snipped]

No, but I've proposed a more restricted version of 12.8p15 which restricts
the optimization to return values.  That version does not break Brian's
testcase.

Jason
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/18
Raw View
On 17 Jun 1997 15:43:13 PDT, jpotter@falcon.lhup.edu (John Potter)
wrote:
>...
>: I would agree with this argument, except that I don't think that even
>: this optimisation is allowed by 12.2, and so it too relies upon
>: 12.8/15.
>
>I don't think so.  12.2/1 lists places where the implementation may
>need to create temporaries.  It requires that the form using the
>temporary must be well formed under the semantics restrictions of the
>language, such as an accessable copy constructor.  Given that, the
>implementation may avoid creating the temporary.  Following the links
>to the places where creation of the temporary is described, there is
>always a link back to 12.2 stating that the temporary may be avoided.
>As I see it, 12.2 covers removal of unnamed temporaries while 12.8/15
>extends this to named copies.  Both clauses are authorized violations
>of the "as if" rule because they can be detected via side effects in
>the ctors/dtor.

I have looked over the draft again and I  don't agree- I wonder if
some other language expert could give their opinion on this. I agree
that in various places there is a hint that temporaries may be elided
(e.g. 12.2/1,  [stmt.return], etc), but the only place that explicitly
mentions eliding temps (not just named temps) is 12.8/15.

(Clarification: I use RVO to refer to eliding an anonymous temp in a
return statement i.e. return T( ), and NRVO to refer to eliding a
named temporary. I believe that these are the definitions in "More
Effective C++" and differ with the usage of RVO as a synonym for
NRVO).

>:(Incidentally, I would be interested in your opinion of my STL
>:pass-by-value example in my response to Jason Merrill in this thread.
>:Do you think 12.8/15 should be allowed to be applied in that case?).
>...
>Optimize out v2 and replace all further uses with v1
>reverse(v1.begin(), v1.end());
>
>Seems like it is allowed.  Scary?  Do I think that it should be
>allowed?  No strong feelings; so, I am willing to allow it to get
>(N)RVO.  Does it scare me?  Not any more than any use of iterators.
>Iterators are abstractions of pointers and just as dangerous.  In STL,
>they export the internals of the container class which is always
>dangerous.  I get scared any time that I declare a variable of type
>iterator.  I try to only pass begin()/end() to functions.  Sometimes I
>use an iterator in a for statement, and become very careful of its use
>in the body.  Any more global use is cause for redesign consideration
>or extreme care.
>..

This highlights our difference in approach to what a standardised
language entails. In your assessment, the standardised language need
only support "well-designed" code and, in particular, only classes
that have either pure value or reference semantics.

OTOH, I think that even "poorly-designed" classes with mixed semantics
or uncommon iterator usage should be supported by the language, to the
extent that any code sample could in principle be proved to be correct
or incorrect (to the extent possible in a language without a formal
semantics).

If there was an unequivical definition of "poorly designed" or even of
"value/reference" semantics, and everyone was aware that the C++
language became unreliable when stepping outside of those definitions
then I would have no qualms about it.
However, as my MV++ and STL examples show, people do write code with
imprecise copy semantics or that hang on to iterators, and the main
problem is that the compiler cannot detect such constructs- the
problems are only manifest in the most obscure means possible i.e. via
subtle bugs introduced when changing compilers at some later date.
That is my main concern, and in the case of the NRVO, I think that
such gotchas will become more common once automatic garbage collection
is more widely used (which can easily lead to memory being retained
after a local variable is destroyed.)

Of course, if one knows that their code has correct copy semantics and
iterator behaviour etc. then it is appropriate for additional
optimisations to be done on that code, but only via an explicit user
request via compiler switches, IMHO.

(Note: that the fact that *extra* temporaries can be introduced by
8.5.3 [dcl.init.ref] when binding a temp to a reference is a
relatively innocuous form of non-determinism (although in an ideal
world even this would be fully specified) in that at least one copy is
still guaranteed so classes with mixed copy semantics will still work-
it is the total elimination of all copies that is the overriding
problem.
I mention this because I get the impression that people feel that
because there is already some non-determinism with temporaries then a
little more can't hurt, which is incorrect.)

Also note that for classes with bitwise copy semantics and only
implicit (compiler-generated) copy constructors- the temps can already
be fully elided under the "as-if" rule.

>: Having said that, I still think that the NRVO and RVO do alter the
>: semantics of return-by-value and I don't agree with your assessment
>: that the  MV_Vector_double class or my example class is broken. If one
>: wants to use a vector class with C arrays then such a design is
>: required.
>
>Well, maybe not broken, but dangerous.  I think that the idea in the
>MV class is to allow a vector on the stack by exporting implementation
>details to the user.  This may be desirable; however, it sure makes it
>easy to produce incorrect code.

Another use is simply to mix C and C++ code e.g. a C function may
return a dynamically allocated C array which we would like to wrap as
a C++ class for a while, and then later get the original C array back
to call further C functions.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Brian Parker <bparker@gil.com.au>
Date: 1997/06/18
Raw View
Martin D Kealey wrote:
>I think this whole thread is getting a bit back-to-front.
>
>Essentially, the problem is not "should the standard allow this
>transformation", but "what does it mean in terms of coding".
>
>What it means in terms of coding is that assignment and copy-constructors
>should be idempotent, and should provide either value or reference
>semantics, but not both.  If both are required, then *two* classes
>should be used.
>

[code showing original example changed to use only pure value and reference
semantics classes snipped]

>> This example is of the same form as my previously posted example, but
>> it is interesting as it occurs using a well-known matrix package.
>
>In which case the package should be sold with a warning label saying that
>you have to use your compiler in non-eliding mode, because the package is
>not fully conforming to the C++ standard as it now exists (we are talking
>about writing the package to what was a draft standard language at the
>time, after all).
>
>> Whilst I wouldn't mind such code changes if I had explicitly enabled a
>> non-standard compiler optimisation, I still find it unsettling that
>> such non-deterministic behaviour is allowed by the standard.
>
>It's only non-deterministic if your classes don't take it into account.
>The rules for avoiding it are not complicated, of similar significance to
>the rule about not returning a reference to a local variable or compiler
>temporary.

I agree that the main point is what does the NRVO mean in terms of coding
rules- as you say, in the presence of the NRVO one *must* ensure that
classes have either pure value or reference semantics, and the MV++ example
failed in a subtle way because it didn't follow those rules. I also agree
that the example could be recast, as you have done, into separate classes
with pure refernce and value semantics (though at some cost in added
complexity).

Where I differ is that I don't think that is the place of the standard to
dictate that such a design rule must be followed. I might perhaps agree if
compilers could somehow warn when designs didn't follow these rules- but
they can't and hence the result would be subtle bugs surfacing at a later
date when the compiler is changed.

I don't agree that these design rules are similar to, say, not returning a
reference to a local as that is a well defined life-time issue, but ensuring
value/reference semantics is a vague design rule. Try defining a precise
definition of "value-semantics" that could be added to the draft- once one
starts using garbage-collection techniques and sharing storage in a complex
fashion the issue is not so clear cut.

In fact, when automatic garbage collection becomes more widespread, I would
expect such mixed value/reference semantics to become much more commmon. For
example, one might design a dynamic vector class with value semantics, but
with garbage collection available users are going to hold onto pointers to
the internal memory after the vector is out of scope and will expect the
internal memory to remain available; so despite the designer's best efforts,
his class becomes a reference to shared dynamic storage.

As always, I add the proviso that all of this applies only to 12.8/15 being
used for the (N)RVO- using it to elide copies on pass-by-value remains
broken despite the semantics of the classes involved.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "Bradd W. Szonye" <bradds@concentric.net>
Date: 1997/06/18
Raw View
Fergus Henderson <fjh@mundook.cs.mu.OZ.AU> wrote in article
<5o7lbs$3a6@mulga.cs.mu.OZ.AU>...
>
> The rule allows compilers to optimize
> away copies if _either_ the source or the target of the copy is not
> used again.

Okay, I finally looked up the rule in question after seeing these examples.
The trouble with 12.8/15 is that, in the presence of possible aliasing,
it's undecidable whether an object is actually used again. Take, for
example:

    void foo(T t)
    {
        T = tval1;
    }

    int main()
    {
        T t = tval0;
        T * tp = &t;
        foo(t);
        cout << *tp << endl;
    }

Does this print tval0 or tval1? It depends on whether the compiler performs
value-parameter optimization and on whether use of tp is a use of t. I
would argue that, even though it's an aliased use, it's still definitely a
use of t, and behaving otherwise would break reasonable programs. This
example is very simple, however; it's not hard to construct examples where
it's not clear--not even to humans--whether an object is used again.
Practically speaking, except for compiler-generated temporaries and objects
which are otherwise about to go out of scope immediately, it's undecidable
whether "either the original object will never again be used." It's hard
even to come up with a reasonable definition of "used."

I wouldn't mind seeing a non-normative note added to the example in 12.8/15
to the effect that: "Determining whether an object is used again is in
general undecidable, and implementations should be conservative in applying
this optimization. In particular, an implementation should ensure that
pass-by-value has the expected semantics in the presence of aliases." It's
weaselly, true, but it would help discourage implementors from breaking
pass-by-value.

Fortunately, the places where an object can "never again be used" are
decidable for the most useful optimizations: variables returned from
functions immediately before they go out of scope, and temporaries
constructed in the course of initializations. Those are the cases where
Machievelli could probably screw up the works, but Murphy is much less of a
problem.

Don't forget that implementors don't want to break your code. That's why
some like Sun and Microsoft are slower to jump on the "new features"
bandwagon discussed elsewhere. Commercial compilers are much less likely to
optimize away pass-by-value than, say, experimental compilers.
--
Bradd W. Szonye
bradds@concentric.net
http://www.concentric.net/~Bradds
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Pete Becker <petebecker@acm.org>
Date: 1997/06/18
Raw View
Fergus Henderson wrote:
>
>
> Nope, your two example classes _are_ different from the outside:
>
>         const C2 c2;
>         const_cast<C2&>(c2).set_data(42);       // fine
>
>         const C1 c1;
>         const_cast<C1&>(c1).set_data(42);       // undefined behaviour
>

Fine, you thought of a way in which my brief example fell a bit short of
perfection. Are you being deliberately obtuse, or are you really missing
the point of this discussion? Either way, I'm through wasting my time on
this discussion.
 -- Pete
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/19
Raw View
On 17 Jun 1997 09:25:34 PDT, Pete Becker <petebecker@acm.org> wrote:
 >
 >> OK, so where did I go wrong?
 >>
 >
 > You know, after I posted this I realized it was too clever. Making
 >destructor_count a global variable makes it clearer. I'm pretty sure
 >that I think static data is not part of an object, but that's an
 >unnecessary complication here.

Actually, in my original MV++ example, the shared data *is* global
data- it becomes a "part" of the vector on initialisation, but its
lifetime extends beyond the lifetime of the vector itself.

 > The real point of all this is this:
 >
 >class C1
 >{
 >public:
 > C() {}
 > void set_data( int i ) { data[0] = i; }
 > int get_data() const { return data[0]; }
 >private:
 > int data[1];
 >};
 >
 >class C2
 >{
 >public:
 > C() : data(new int) {}
 > // suitable assignment operator and copy constructor, of course...
 > ~C2() { delete data; }
 >
 > void set_data( int i ) { data[0] = i; }
 > int get_data() const { return data[0]; }
 >private:
 > int *data;
 >};
 >
 > I don't see any fundamental difference between these two classes,
 >merely a difference in implementation. A definition of "object" that
 >says they have different properties is wrong. Internally they are
 >somewhat different, but from the outside they present exactly the same
 >interface and exactly the same states and behaviors.
 > -- Pete

I agree 100%, a standard that treated these two classes differently
would simply be wrong- and hence the language would not be useful for
any critical application. The trouble is that not everyone shares that
view and would be willing to accept the bugs 12.8/15 introduces- and
hence take the literal meaning of "object" in 12.8/15 i.e. only the
memory created on initial definition.

The problem with rewording 12.8/15 to use the definition of "object"
that includes all memory used indirectly is that it is not
well-defined. The example above is clear, but when the various bits of
memory used by an object are shared and/or have different lifetimes
(and particularly when you add you add garbage-collection to this mix)
then it is not clear when a bit of memory is a "part" of the object
and when it is simply being temporarily used.
And if 12.8/15 does not have a precise definition of object, then it
would always leave open the possibility of a bug-introducing loophole,
and so one would always need to code defensively anyway.

A standard need to define precise semantics- I think it would be a
grave mistake to leave 12.8/15 in the draft as is.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: James Kuyper <kuyper@wizard.net>
Date: 1997/06/19
Raw View
Pete Becker wrote:
 >
 > Fergus Henderson wrote:
 > >
 > > Pete Becker <petebecker@acm.org> writes:
 > >
 > > >Fergus Henderson wrote:
 > > > >
 > > > > I see three alternatives:
 > > > >
 > > > >         1a.  Leave the rule in there, but clarify that "object"
 > > > >              means "Humpty-Dumpty object" for the purposes of this
 > > > >              rule.
 > > >
 > > >Sigh. More ad hominem argument.
 > >
 > > My copy of the Concise Oxford Dictionary defines "ad hominem"
 > > as "To the man, personal".  My language may be colorful,
 > > but it is directed squarely at the issues, not at the people
 > > involved.  I have considerable respect for you and the other
 > > people who have been involved in this debate.
 >
 > My Oxford Encyclopedic Dictionary also says "(of an argument) appealing
 > to the emotions and not to reason", which is how I was using it. You
 > need to upgrade your dictionary. <g>
 >         -- Pete

That definition pays no attention to the origin of the term. It sounds
like so many people misunderstood and misused the term that their
misunderstanding became enshrined as an alternate definition.

In any event, that definition doesn't seem to apply either. Where is the
appeal to emotion? By calling it a "Humpty-Dumpty object", he was
emphasizing
a) that this would constitute a change in the definition of an object.
b) that this revised definition could not applied consistently
throughout the standard, without wrecking the language.

Point a) is unambiguously true. Point b) is legitimately debatable, but
I agree with it.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Jason Merrill <jason@cygnus.com>
Date: 1997/06/19
Raw View
>>>>> Brian Parker <bparker@gil.com.au> writes:

> Martin D Kealey wrote:

>> What it means in terms of coding is that assignment and copy-constructors
>> should be idempotent, and should provide either value or reference
>> semantics, but not both.  If both are required, then *two* classes
>> should be used.

> I don't agree that these design rules are similar to, say, not returning a
> reference to a local as that is a well defined life-time issue, but ensuring
> value/reference semantics is a vague design rule. Try defining a precise
> definition of "value-semantics" that could be added to the draft- once one
> starts using garbage-collection techniques and sharing storage in a complex
> fashion the issue is not so clear cut.

There's no need to define "value-semantics" in the draft; it is enough for
the draft to say that a program may not rely on the copy constructor being
called in (N)RVO situations.

David Vandevoorde's version of valarray is a good example of the
value/reference split.

Jason
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/19
Raw View
On 18 Jun 97 02:15:53 GMT, Jason Merrill <jason@cygnus.com> wrote:

>>>>>> Brian Parker <bparker@gil.com.au> writes:
>
>> Trouble is, the STL also plays aliasing games. In fact, many classes
>> using dynamically allocated memory would fall prey to the 12.8/5 bugs.
>>  I repeat here a previously posted example using STL (it involves
>> 12.8/5 being used to elide the copy on pass-by-value).
>
>In this testcase, the problem occurs because even though 'v' is not
>directly used after the call, references to data it controls are.  This
>would not be a problem if 'v' went out of scope immediately after the
>call, because any references to its data are undefined after it goes out of
>scope.

Unless some garbage collection scheme is used such that some of the
object's dynamically-allocated memory is retained beyond the local
scope (or if the object uses some global memory in its implementation-
see the MV++ example)

>  Thus, I suggest that 12.8/15 should be reworded to read
>
>15Whenever an automatic (_basic.stc.auto_) class object is copied and the
>  original object and the copy have the same type, and the block containing
>  the original object exits (_stmt.jump_) immediately after the copy is
>  created, an implementation is permitted to treat the original and the
>  copy as two different ways of referring to the same object and not
>  perform a copy at all.  In that case, the object is destroyed when the
>  copy would have been destroyed without the optimization.
>
>I think this only allows the RVO.

This rewording to limit 12.8/15 to just the NRVO and RVO would render
it much safer, and I think that at a minimum this is necessary.

However, as I have shown elsewhere in this thread, the same issues
arise with the NRVO and RVO, albeit in a much more limited way. In
particular, the problem examples involve designs which don't maintain
a strict separation of value and reference semantics. Whilst these
examples may be of little significance now, one of my concerns is that
as automatic garbage collection becomes more widely used then these
problems with the (N)RVO will become more widespread
e.g. the STL vector class currently has value semantics, but when a
garbage collector is linked in, people are going to hold onto
iterators of automatic vectors and, rightly, expect the pointed at
memory to be retained beyond the local scope.
(N.B. although garbage collection is strictly outside of the standard,
AFAIK it is at least supposed to be tolerant of it).

As an aside, I wonder if anyone is aware of other garbage-collected
languages that support value semantics class objects, where these same
issues would arise?

Just some further comments on the actual wording-

(1) It should be limited to copies using the copy constructor only-
copy assignment operator calls are never implicitly created by the
compiler and hence shouldn't be elidable.

(2) By my reading, 12.8/15 is also used to elide copies of tempoaries
on initialisation. e.g.

class D {

 D(int) {...};
 D(const D&) { ... };
};

The initialisation,
D d = 1;

has the semantics-
D d = D(1);
i.e. a temporary D is created which is then copy constructed into d.

I believe that 12.8/15 is needed to allow the common optimisation of
constructing the temporary D directly into d and so eliding a copy,
and so if you want to continue to allow this then some addition words
would need to be added to 12.8/15.
(Actually, I am not certain if 12.8/15 is needed in this case; can
anyone shed some light on this?)

(Note that in same way that the (N)RVO can cause unexpected behaviour,
so can the above optimisation, so ideally it too should not be applied
in unsafe cases such as the MV++ example.)

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: James Kanze <james-albert.kanze@vx.cit.alcatel.fr>
Date: 1997/06/19
Raw View
Jason Merrill <jason@cygnus.com> writes:

|>  >>>>> Brian Parker <bparker@gil.com.au> writes:
|>
|>  > Trouble is, the STL also plays aliasing games. In fact, many classes
|>  > using dynamically allocated memory would fall prey to the 12.8/5 bugs.
|>  >  I repeat here a previously posted example using STL (it involves
|>  > 12.8/5 being used to elide the copy on pass-by-value).
|>
|>  In this testcase, the problem occurs because even though 'v' is not
|>  directly used after the call, references to data it controls are.  This
|>  would not be a problem if 'v' went out of scope immediately after the
|>  call, because any references to its data are undefined after it goes out of
|>  scope.  Thus, I suggest that 12.8/15 should be reworded to read
|>
|>  15Whenever an automatic (_basic.stc.auto_) class object is copied and the
|>    original object and the copy have the same type, and the block containing
|>    the original object exits (_stmt.jump_) immediately after the copy is
|>    created, an implementation is permitted to treat the original and the
|>    copy as two different ways of referring to the same object and not
|>    perform a copy at all.  In that case, the object is destroyed when the
|>    copy would have been destroyed without the optimization.
|>
|>  I think this only allows the RVO.

It may not even allow that, if the function has local variables.  There
may be calls to other destructors between the moment the return value is
copied and the destructor of the object being copied.

--
James Kanze      home:     kanze@gabi-soft.fr        +33 (0)1 39 55 85 62
                 office:   kanze@vx.cit.alcatel.fr   +33 (0)1 69 63 14 54
GABI Software, Sarl., 22 rue Jacques-Lemercier, F-78000 Versailles France
            -- Conseils en informatique industrielle --
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/06/19
Raw View
On 18 Jun 97 02:15:53 GMT, Jason Merrill <jason@cygnus.com> wrote:

: >>>>> Brian Parker <bparker@gil.com.au> writes:

: > Trouble is, the STL also plays aliasing games. In fact, many classes
: > using dynamically allocated memory would fall prey to the 12.8/5 bugs.
: >  I repeat here a previously posted example using STL (it involves
: > 12.8/5 being used to elide the copy on pass-by-value).

: In this testcase, the problem occurs because even though 'v' is not
: directly used after the call, references to data it controls are.  This
: would not be a problem if 'v' went out of scope immediately after the
: call, because any references to its data are undefined after it goes out of
: scope.  Thus, I suggest that 12.8/15 should be reworded to read

: 15Whenever an automatic (_basic.stc.auto_) class object is copied and the
:   original object and the copy have the same type, and the block containing
:   the original object exits (_stmt.jump_) immediately after the copy is
:   created, an implementation is permitted to treat the original and the
:   copy as two different ways of referring to the same object and not
:   perform a copy at all.  In that case, the object is destroyed when the
:   copy would have been destroyed without the optimization.

: I think this only allows the RVO.

It clearly requires a copy for g(t2).  Not so sure about f(t1).  The
wording does force an unused copy to be made.  Did you intend that?  I
am not worried about it since I did not expect to get this anyway.  It
would be nice to have an example which does work.
 Thing h (Thing t) {
  Thing tmp(t);
  // maybe do something with tmp
  return tmp;
  }
Here tmp can be removed and the return value used in place of tmp with
no copy.

It is also simple to disable by doing what many people recommend
anyway and make the return a Thing const.  Then the types are not the
same.

John
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/20
Raw View
On 18 Jun 97 02:13:20 GMT, Barry Margolin <barmar@bbnplanet.com>
wrote:

>In article <33a5f2fc.3455132@news.ipswich.gil.com.au>,
>Brian Parker <bparker@gil.com.au> wrote:
>>As far as I can see, 12.8/15 could only be unambiguously safely
>>applied by a compiler for a class with bitwise copy semantics, i.e.
>>either the class and all its members and base classes have implicitly
>>defined copy constructors, or any user-provided explicit copy
>>constructors must involve only simple assignment of members. In these
>>cases, though, the copy could already be elided by the "as if" rule.
>
>I suspect this is the optimization that this section is trying to allow.
>However, the as-if rule is not sufficient to permit it, because the as-if
>rule is violated if the class or any of its members or bases have side
>effects in their destructors (as in the destructor_count++ example that
>someone posted).

That's true- I forgot about user-provided destructors. My statement
above only applies if all destructors are also implicit.

>
>I think that NRVO should be reworded to specify that if the copy
>constructor has bitwise copy semantics, you can't depend on destructors
>being run during an assignment.

That may be a useful approach. If the copy constructor has bit-wise
semantics and the destructor is empty then the optimisations of
12.8/15 could already be done by the "as if" rule, but your proposal
would also allow them where there is a non-empty destructor.
I agree that that would be quite safe- the only question is whether
the additional optimisations allowed by this would be useful in
practice.

This would certainly solve the problems with 12.8/15, though.

Anyone else have any comments on this?

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/20
Raw View
On 18 Jun 1997 09:06:47 PDT, "Bradd W. Szonye" <bradds@concentric.net>
wrote:
 >...
 >Okay, I finally looked up the rule in question after seeing these examples.
 >The trouble with 12.8/15 is that, in the presence of possible aliasing,
 >it's undecidable whether an object is actually used again. Take, for
 >example:
 >
 >    void foo(T t)
 >    {
 >        T = tval1;
 >    }
 >
 >    int main()
 >    {
 >        T t = tval0;
 >        T * tp = &t;
 >        foo(t);
 >        cout << *tp << endl;
 >    }
 >
 >Does this print tval0 or tval1? It depends on whether the compiler performs
 >value-parameter optimization and on whether use of tp is a use of t. I
 >would argue that, even though it's an aliased use, it's still definitely a
 >use of t, and behaving otherwise would break reasonable programs.
Yes, this example is non-controversial- t is definitely used.

 >example is very simple, however; it's not hard to construct examples where
 >it's not clear--not even to humans--whether an object is used again.
 >Practically speaking, except for compiler-generated temporaries and objects
 >which are otherwise about to go out of scope immediately, it's undecidable
 >whether "either the original object will never again be used."
Actually, it's not even absolutely clear in the case of objects about
to go out of scope- in a garbage-collected implementation memory can
be retained beyond the local scope (or, as in the MV++ example, some
of the object's memory may be statically allocated).

 >It's hard
 >even to come up with a reasonable definition of "used."
 >
 >I wouldn't mind seeing a non-normative note added to the example in 12.8/15
 >to the effect that: "Determining whether an object is used again is in
 >general undecidable, and implementations should be conservative in applying
 >this optimization. In particular, an implementation should ensure that
 >pass-by-value has the expected semantics in the presence of aliases." It's
 >weaselly, true, but it would help discourage implementors from breaking
 >pass-by-value.
 >
 >Fortunately, the places where an object can "never again be used" are
 >decidable for the most useful optimizations: variables returned from
 >functions immediately before they go out of scope, and temporaries
 >constructed in the course of initializations. Those are the cases where
 >Machievelli could probably screw up the works, but Murphy is much less of a
 >problem.

This was one of my original options for a fix for 12.8/15 too, but I
now don't think it is useful as, being undecidable, it is ambiguous
and hence has no place in a standard. For example, your assessment
that the optimisation is safe in the case of the NRVO is not
universally shared (well, it appears to be shared by everyone in the
universe except me that is :-) ) and so a strict interpretation would
also exclude the (N)RVO and hence one may just as well just remove
12.8/15.

If it is decided to allow the (N)RVO then that would need an explicit
paragraph allowing it, as in Jason Merrill's post, rather than trying
to mend 12.8/15, IMHO.

 >
 >Don't forget that implementors don't want to break your code. That's why
 >some like Sun and Microsoft are slower to jump on the "new features"
 >bandwagon discussed elsewhere. Commercial compilers are much less likely to
 >optimize away pass-by-value than, say, experimental compilers.

That's the danger though- code will be developed on a compiler that
doesn't support these "optimisations" and it may only be many years
later when the code is in production use that compilers which support
these optimisations are used and these obscure bugs are manifest.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/20
Raw View
On 19 Jun 97 02:08:34 GMT, Jason Merrill <jason@cygnus.com> wrote:

>There's no need to define "value-semantics" in the draft; it is enough for
>the draft to say that a program may not rely on the copy constructor being
>called in (N)RVO situations.
>
>David Vandevoorde's version of valarray is a good example of the
>value/reference split.
>
>Jason

Well yes, in the final analysis if the standard says that copies can
be elided in the (N)RVO case then one will need to take that into
account when designing classes. I wasn't seriously suggesting that
"value-semantics" be defined in the standard, just that such a design
concept underlies the (N)RVO and if one ignores it then subtle bugs
can be introduced.

The main danger with 12.8/15 is its use on pass by value; as long as
that is fixed as in your suggested rewording of 12.8/15, then my main
concern will have been salved- the (N)RVO issue is of somewhat lesser
significance (though likely to become more important as
garbage-collection is increasingly used IMHO), and, being applicable
in a more limited context, its effects can be more easily predicted.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/20
Raw View
In an email response, Martin D Kealey wrote:
 >...
 >> I don't agree that these design rules are similar to, say, not returning a
 >> reference to a local as that is a well defined life-time issue, but ensuring
 >> value/reference semantics is a vague design rule. Try defining a precise
 >> definition of "value-semantics" that could be added to the draft- once one
 >> starts using garbage-collection techniques and sharing storage in a complex
 >> fashion the issue is not so clear cut.
 >
 >I would tend to phrase such a rule in terms of "resource ownership".  If
 >an object exclusively owns a resource, then it's part of its value, and
 >value semantics apply; conversely, if it doesn't exclusively own the
 >resource, the resource is not part of its value, and reference or pointer
 >semantics apply.  If value semantics apply, then the copy constructor
 >should make a deep copy, or at least arrange so that any attempt at
 >writing will cause one to be made.  (This later option only applies if
 >the resource can only be shared with an object of a co-dependant class
 >type.)  Failure to do so leads to undefined behaviour.  I think this
 >isn't too difficult, but unfortunately it has to be phrased in terms of a
 >bunch of concepts that currently aren't in the C++ standard.

Two points:
(1) I don't think that the definition of "owns" would be easily
defined in C++.

(2) I disagree with your definition of the value of an object based on
ownership. In fact, I think this is the main difference in our
respective points of view.
I think that the value of an object is essentially defined by the copy
constructor- it is what the copy-constructor copies (excepting global
memory shared by all instances). In most cases this corresponds with
resource ownership, but not always.

To give another example (which is actually isomorphic to my MV++
example), imagine an STL-like vector in a garbage-collected
environment. Now the dynamically-allocated memory inside the vector is
not actually owned by any object- it is owned by the system. However,
we would like the vector to have value semantics and so the copy
constructor will make a copy of the internal memory; in fact,
initially we can logically view the memory as being "owned" by the
vector. Now imagine that an iterator to the vector's internal memory
is returned and kept beyond the scope of the local vector object

i.e.

int* p;
    {
        vector<int> v(10);
        p = v.begin();
    }
int n = *p;  // internal memory of v still valid here due to garbage
collection

When the iterator is returned via v.begin() we can view the memory as
now having shared ownership between p and v, and outside of the nested
scope it is "owned" just by p.

In this case, although the ownership of the internal memory block
changes, the value of v as defined by the copy constructor remains the
same and well-defined throughout.

Whilst such a definition of object may not correspond to a pure value
as used in a functional language, in an imperative language that
combines high and low level features such as C++, the above example is
a valid design, IMHO.

 >
 >Having looked at a number of pure-functional languages, the ability to
 >regard values as independent of their names seems to be a key concept,
 >and is also very important to optimisation.
 >
 >The distinction between "value" and "reference" only make sense when one
 >has mutable objects (so that *when* you copy it has significance).  It
 >seems to me that C++ is heading in the right direction by trying to
 >achieve name-independence in values; basically using "const" to mean
 >"value", and absence of "const" to mean "object", and I would like to
 >see that followed to its logical conclusion.  (Although that is
 >complicated by the idea that a reference (pointer) can itself by "const",
 >while refering to a non-const object.)
 >

The distinction between "value" and "object" is explicitly made to
some degree in the draft, where "value" corresponds with rvalue (i.e.
temporary) and "object" corresponds with lvalue (i.e. a variable or
expression with an associated storage location such that it can have
its address taken, assigned to etc.). The problem is that class type
rvalues can actually be modified via member function calls, so, as you
suggest, the closest thing to a "value" in C++ for class types is a
const rvalue/temporary.

 >How would this be as a compromise:
 >
 >(1) Introduce "const constructor", whereby the const-ness or otherwise of
 >    the object being declared (or new'ed) is used to determine which
 >    constructor is invoked.
 >
 >(2) Any "const" copy-constructor can be elided under the conditions of
 >    non-overlapping usage, on the assumption that it represents only a
 >    value-copy (where that "value" might include a "pointer" to some
 >    shared resources).
 >
 >I suspect that the const-ness of members and *this within a const
 >constructor is a bit of a difficulty; either you have some cludge
 >allowing them to be modified, or you treat them as strictly const and
 >insist that they acquire their values in the initialiser list.  The
 >problem is essentially that the initialiser list is rather clumsy: it
 >would be far easier to be able to initialise other values in-between:
 >
 >    class Foo {
 >        int x;
 >        bool y;
 >     public:
 >        // current style of constructor:
 >        Foo( const char * filename ) : x(), y(false)
 >        {
 >            ifstream in(filename); string b; in >> b;
 >            x = atoi(b.c_str());
 >        }
 >        // new style of constructor:
 >        const Foo( const char * filename ) :
 >            {
 >                ifstream in(filename); string b; in >> b;
 >            } x(atoi(b.c_str())),
 >            y(true)
 >        {}
 >    };
 >
 >This obviously needs some work on the block-scope rules -- anything inside
 >the outermost {} would be visible, such as "b" in the above example.
 >
 >Would this be reasonable?

Whilst, having a const constructor would allow a better distinction
between rvalue and lvalue usage for class types, I think the issues of
object definition are really orthogonal to the rvalue/lvalue issue.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: David Vandevoorde <daveed@vandevoorde.com>
Date: 1997/06/20
Raw View
Brian Parker wrote:
[...]
> I have looked over the draft again and I  don't agree- I wonder if
> some other language expert could give their opinion on this. I agree
> that in various places there is a hint that temporaries may be elided
> (e.g. 12.2/1,  [stmt.return], etc), but the only place that explicitly
> mentions eliding temps (not just named temps) is 12.8/15.

[expr.call]/4 mentions that a conversion followed by a by-value
initialization for class-type arguments to a call can be folded
into just a conversion.

 Daveed
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Jason Merrill <jason@cygnus.com>
Date: 1997/06/20
Raw View
 >>>>> James Kanze <james-albert.kanze@vx.cit.alcatel.fr> writes:

 > Jason Merrill <jason@cygnus.com> writes:

 >>  15Whenever an automatic (_basic.stc.auto_) class object is copied and
 >>    the original object and the copy have the same type, and the block
 >>    containing the original object exits (_stmt.jump_) immediately after
 >>    the copy is created, an implementation is permitted to treat the
 >>    original and the copy as two different ways of referring to the same
 >>    object and not perform a copy at all.  In that case, the object is
 >>    destroyed when the copy would have been destroyed without the
 >>    optimization.
 >>
 >>  I think this only allows the RVO.

 > It may not even allow that, if the function has local variables.  There
 > may be calls to other destructors between the moment the return value is
 > copied and the destructor of the object being copied.

That's why I said "block exits" rather than "object is destroyed".

Jason
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/20
Raw View
On 18 Jun 97 06:15:53 GMT, fjh@mundook.cs.mu.OZ.AU (Fergus Henderson)
wrote:

>Nope, your two example classes _are_ different from the outside:
>
> const C2 c2;
> const_cast<C2&>(c2).set_data(42); // fine
>
> const C1 c1;
> const_cast<C1&>(c1).set_data(42); // undefined behaviour

That's a good point. The fact that a compiler cannot make constness
"deep" (although logically it should) is exactly analogous to the
problem of determining what memory an object consists of.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/20
Raw View
On 19 Jun 1997 09:06:42 PDT, I bparker@gil.com.au (Brian Parker)
wrote:

>(1) It should be limited to copies using the copy constructor only-
>copy assignment operator calls are never implicitly created by the
>compiler and hence shouldn't be elidable.
>
>(2) By my reading, 12.8/15 is also used to elide copies of tempoaries
>on initialisation. e.g.
>
>class D {
>
> D(int) {...};
> D(const D&) { ... };
>};
>
>The initialisation,
>D d = 1;
>
>has the semantics-
>D d = D(1);
>i.e. a temporary D is created which is then copy constructed into d.
>
>I believe that 12.8/15 is needed to allow the common optimisation of
>constructing the temporary D directly into d and so eliding a copy,
>and so if you want to continue to allow this then some addition words
>would need to be added to 12.8/15.
>(Actually, I am not certain if 12.8/15 is needed in this case; can
>anyone shed some light on this?)

I wonder if a better rewording of 12.8/15 would be to simply add a
sentence preventing its use on pass-by-value.

i.e. change 12.8/15 to -

"Whenever a class object [intro.object] is copied via a *copy
constructor* and the original object and the copy have the same type,
*except on pass-by-value (5.2.2 Function call)* if the implementation
can prove ... (rest of wording the same)."

And remove the current examples, replacing them with a NRVO example.

This would still allow copies to be elided on NRVO, RVO and
initialisation of variables.

Any comments?

,Brian Parker (bparker@gil.com.au)


---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: James Kanze <james-albert.kanze@vx.cit.alcatel.fr>
Date: 1997/06/20
Raw View
James Kuyper <kuyper@wizard.net> writes:

 |>  Pete Becker wrote:
 |>   >
 |>   > Fergus Henderson wrote:
 |>   > >
 |>   > > Pete Becker <petebecker@acm.org> writes:
 |>   > >
 |>   > > >Fergus Henderson wrote:
 |>   > > > >
 |>   > > > > I see three alternatives:
 |>   > > > >
 |>   > > > >         1a.  Leave the rule in there, but clarify that "object"
 |>   > > > >              means "Humpty-Dumpty object" for the purposes of
 |>   > > > >              this rule.
 |>   > > >
 |>   > > >Sigh. More ad hominem argument.
 |>   > >
 |>   > > My copy of the Concise Oxford Dictionary defines "ad hominem"
 |>   > > as "To the man, personal".  My language may be colorful,
 |>   > > but it is directed squarely at the issues, not at the people
 |>   > > involved.  I have considerable respect for you and the other
 |>   > > people who have been involved in this debate.
 |>   >
 |>   > My Oxford Encyclopedic Dictionary also says "(of an argument) appealing
 |>   > to the emotions and not to reason", which is how I was using it. You
 |>   > need to upgrade your dictionary. <g>
 |>   >         -- Pete
 |>
 |>  That definition pays no attention to the origin of the term. It sounds
 |>  like so many people misunderstood and misused the term that their
 |>  misunderstanding became enshrined as an alternate definition.
 |>
 |>  In any event, that definition doesn't seem to apply either. Where is the
 |>  appeal to emotion? By calling it a "Humpty-Dumpty object", he was
 |>  emphasizing
 |>  a) that this would constitute a change in the definition of an object.
 |>  b) that this revised definition could not applied consistently
 |>  throughout the standard, without wrecking the language.
 |>
 |>  Point a) is unambiguously true. Point b) is legitimately debatable, but
 |>  I agree with it.

I think you are forgetting the historical and literary references that
"Humpty-Dumpty" conjures up in this case.  If I remember right, the term
was first applied to C++ by Jim Adcock, in reference to logical const (=
"Humpty-Dumpty" const, i.e. "When I use a const, it means exactly what I
want it to mean.")  And the obvious reference is to Humpty-Dumpty in
Lewis Carroll ("Through the Looking Glass", I think): "When I use a
word, it means exactly what I want it to mean."

I would consider that equating a certain concept to the logic of
Humpty-Dumpty in Lewis Carroll is "appealing to the emotions and not to
reason".  And I can hardly believe that Fergus used the expression
without expecting the reference to be recognized, especially since both
Pete and Fergus were contributors to comp.lang.c++ back when Jim Adcock
was using the term in that forum, and so had certainly seen it in this
context.

None of which, IMHO, invalidates Fergus' point.

--
James Kanze      home:     kanze@gabi-soft.fr        +33 (0)1 39 55 85 62
                 office:   kanze@vx.cit.alcatel.fr   +33 (0)1 69 63 14 54
GABI Software, Sarl., 22 rue Jacques-Lemercier, F-78000 Versailles France
            -- Conseils en informatique industrielle --
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/20
Raw View
On 20 Jun 97 08:29:59 GMT, David Vandevoorde <daveed@vandevoorde.com>
wrote:

>Brian Parker wrote:
>[...]
>> I have looked over the draft again and I  don't agree- I wonder if
>> some other language expert could give their opinion on this. I agree
>> that in various places there is a hint that temporaries may be elided
>> (e.g. 12.2/1,  [stmt.return], etc), but the only place that explicitly
>> mentions eliding temps (not just named temps) is 12.8/15.
>
>[expr.call]/4 mentions that a conversion followed by a by-value
>initialization for class-type arguments to a call can be folded
>into just a conversion.

Yes, but similarly to the cases of [stmt.return] and [dcl.init], it
defers to 12.2 for license to perform the temporary elision, but 12.2
itself only has a (non-normative I presume) example demonstrating the
kinds of optimisations possible. My interpretation is that 12.2 is, in
fact, itself relying upon 12.8/15 to allow these examples.

Indeed, one worrying example in 12.2/2 clearly relies upon 12.8/15-
where it hints that copies via an assigment statement may also be
elided where there is no aliasing (which of course a compiler cannot
in general determine).

I still think that all roads lead to 12.8/15 when it comes to
temporary elision- I wonder if this was indeed the intention, or is
the draft just unclear on this issue (or is it just me) ?

This is actually a very important issue with respect to the
ramifications of the rewriting or deleting of 12.8/15- would deleting
12.8/15 still allow temporary elision on function return,
initialisation and conversions on function calls?
I think not, and hence if 12.8/15 is rewritten it will need to
explicitly allow for these cases if desired.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: "Bill Wade" <bill.wade@stoner.com>
Date: 1997/06/20
Raw View
Brian Parker <bparker@gil.com.au> wrote in article
> [deleted]
> I wonder if a better rewording of 12.8/15 would be to simply add a
> sentence preventing its use on pass-by-value.
>
> i.e. change 12.8/15 to -
>
> "Whenever a class object [intro.object] is copied via a *copy
> constructor* and the original object and the copy have the same type,
> *except on pass-by-value (5.2.2 Function call)* if the implementation
> can prove ... (rest of wording the same)."

// String class.  Uses by-value semantics.  Normally uses a shared
representation (copy on write).
// Buffer can be locked, meaning don't use a shared rep (somebody has a
pointer into the string.
class myString
{
  // If locked somebody has a reference to our rep.  Change to unshared
implemenation
  bool locked;
  stringRep* rep;
public:
  myString(const MyString& that); // Will check that.locked to see if we
can share

  // Unshare rep, set locked flag, return pointer into our buffer.  Buffer
remains valid until a non-const
  // member is called, or we are destructed, whichever comes first.
  char* Lock();
  void Unlock(){ locked = false; }
  ... other useful members ...
};

void foo()
{
  myString a("cat");
  char* buf = a.Lock();
  myString b(a);  // a is never used again? Optimize.
  strcpy(buf, "dog");
  assert(b == "cat");
}

It seems that this code is "reasonable" in the absence of the alias
optimization.  However it doesn't work in the presence of the optimization.
 I don't think changing wording to "function call" helps.

Even with examples like this I'm not sure I oppose the optimization, but it
certainly has me worried.  This particular code can be fixed by using an
object (which maintains a reference to 'a') to manage the Lock/Unlock
section:

class locker
{
  myString& the_str;
public:
  locker(myString& s): the_str(s){ the_str.Lock(); }
  ~locker(){ the_str.Unlock(); }
  void Unlock(){ the_str.Unlock(); }
  char* Get(){ the_str.Get(); }
};

where locker is a friend of myString, and the myString members Lock,
Unlock, and Get are private.

void foo()
{
  myString a("cat");
  locker l(a);
  char* buf = l.Get();
  myString b(a);  // l holds a reference to a which ~l will access.  Don't
optimize.
  strcpy(buf, "dog");
  assert(b == "cat");
}
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/06/20
Raw View
On 19 Jun 1997 09:06:42 PDT, bparker@gil.com.au (Brian Parker) wrote:

: However, as I have shown elsewhere in this thread, the same issues
: arise with the NRVO and RVO, albeit in a much more limited way.

Just trying to clear your terms.
 T f (T t) {
  T tmp(t);
  return tmp;
  }
Removing tmp and constructing the copy in the return value is called
"return value optimization" in D&E and MEC++.  It is called "named
return value optimization" by Lippman in IC++OM and C++Report.  Since
you are using both terms, I assume that they mean two different things
to you.
 T f (T t) {
  return T(t);
  }
I think that you are calling removal of the unnamed temporary from the
return RVO and the one above NRVO.  I think that these are two
different issues and the former requires 12.8/15 while the latter
requires only 12.2/1.  Could you clarify your terms?  I think that you
asked for clarification of that and other unnamed temp removals and
maybe someone else will address that.

John
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: fjh@murlibobo.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/20
Raw View
"Bradd W. Szonye" <bradds@concentric.net> writes:

>Okay, I finally looked up the rule in question after seeing these examples.
>The trouble with 12.8/15 is that, in the presence of possible aliasing,
>it's undecidable whether an object is actually used again.

That's not necessarily a problem.  Equivalence of programs is also
undecidable, but compilers perform equivalence-preserving transformations
all the time.  In fact, most interesting program properties are
undecidable.

The trouble with 12.8/15 as interpreted using the standard definition
of object is that it causes some programs to have anomalous and unexpected
semantics, as exhibited by the many examples in this thread.

The trouble with 12.8/15 as interpreted using Pete Becker's interpretation
of "object" is that it is not well-defined.  Being undecidable would be
OK, but being not well-defined is not.

>Take, for
>example:
>
>    void foo(T t)
>    {
>        T = tval1;
>    }
>
>    int main()
>    {
>        T t = tval0;
>        T * tp = &t;
>        foo(t);
>        cout << *tp << endl;
>    }
>
>Does this print tval0 or tval1? It depends on whether the compiler performs
>value-parameter optimization and on whether use of tp is a use of t. I
>would argue that, even though it's an aliased use, it's still definitely a
>use of t, and behaving otherwise would break reasonable programs.

Yes, I think everyone agrees that this constitutes use of the object.

>This example is very simple, however; it's not hard to construct examples
>where it's not clear--not even to humans--whether an object is used again.

Sure, but that's not a problem.  A compiler should optimize such examples
only if it can prove that the object won't be used again.
For example, consider the following code:

 T foo() {
  T x;
  T x2(x);
  if (there_is_a_counter_example_to_fermats_last_theorem()) {
   use(x);
  }
  return x2;
 }

In this example, it is very difficult to figure out whether or not
`x' will be used again.  If the compiler can prove that the function
there_is_a_counter_example_to_fermats_last_theorem() will never return
`true', then it can use 12.8/15 to use the same storage for `x'
and `x2'.  However, it should only apply this optimization if it can
indeed find such a proof (and in general, finding such a proof might be
arbitrarily difficult).

>I wouldn't mind seeing a non-normative note added to the example in 12.8/15
>to the effect that: "Determining whether an object is used again is in
>general undecidable, and implementations should be conservative in applying
>this optimization. In particular, an implementation should ensure that
>pass-by-value has the expected semantics in the presence of aliases." It's
>weaselly, true, but it would help discourage implementors from breaking
>pass-by-value.

If that is the intent, then it should be expressed in normative text,
not in a non-normative note.  The standard should express a clear
contract between users and implementors.  There is a place for
implementation advice ("should" rather than "shall") but I think that
the standard should clearly define the semantics of basic features of
the language like pass-by-value.

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Jason Merrill <jason@cygnus.com>
Date: 1997/06/20
Raw View
>>>>> Brian Parker <bparker@gil.com.au> writes:

> On 18 Jun 97 02:15:53 GMT, Jason Merrill <jason@cygnus.com> wrote:

>> In this testcase, the problem occurs because even though 'v' is not
>> directly used after the call, references to data it controls are.  This
>> would not be a problem if 'v' went out of scope immediately after the
>> call, because any references to its data are undefined after it goes out of
>> scope.

> Unless some garbage collection scheme is used such that some of the
> object's dynamically-allocated memory is retained beyond the local
> scope (or if the object uses some global memory in its implementation-
> see the MV++ example)

I'm talking about a basic principle here: when the life of an object
expires, references to its contents are undefined.  A garbage-collected
object is not 'automatic', so it doesn't matter to the issue at hand.
Let's talk about this version of C++.

Any global memory the object uses is not "data it controls".  The problem
with the MV++ testcase is, as I have said before, that it relies on the
copy constructor to create a real array out of a fake array, which is too
clever by half.  MV++ should use proxy classes.

> However, as I have shown elsewhere in this thread, the same issues
> arise with the NRVO and RVO, albeit in a much more limited way. In
> particular, the problem examples involve designs which don't maintain
> a strict separation of value and reference semantics.

Yes.

> Whilst these examples may be of little significance now, one of my
> concerns is that as automatic garbage collection becomes more widely used
> then these problems with the (N)RVO will become more widespread

Again, objects subject to garbage collection are presumably allocated with
'new' and destroyed (by the collector) with 'delete', so 12.8p15 does not
apply to them.

> (N.B. although garbage collection is strictly outside of the standard,
> AFAIK it is at least supposed to be tolerant of it).

It is possible write a conservative collector and use it in conformant
C++.  Changing 'automatic' variables to have garbage-collection semantics,
on the other hand, is such a wild departure from the standard that I can't
imagine it ever happening without changing the name of the language to
something with "Java" in it.

> As an aside, I wonder if anyone is aware of other garbage-collected
> languages that support value semantics class objects, where these same
> issues would arise?

As I understand it, in a garbage-collected language like Java you almost
never return anything by copying it; what would be the point?  When you
create a new matrix in your plus method, you just return a reference to the
new matrix, because it's garbage-collected.  It won't get destroyed at the
end of the function like a C++ auto variable, and the caller doesn't have
to worry about deleting it, like a C++ dynamically allocated object.  The
issues we are discussing here simply don't crop up.

In summary, garbage collection is irrelevant to this discussion.

> Just some further comments on the actual wording-

> (1) It should be limited to copies using the copy constructor only-
> copy assignment operator calls are never implicitly created by the
> compiler and hence shouldn't be elidable.

Hmm...I had assumed that 12.8/15 already only applied to the copy
constructor, but I guess I was wrong.  I don't see any compelling reason
for it to apply to the copy assignment operator, so OK.

> (2) By my reading, 12.8/15 is also used to elide copies of tempoaries
> on initialisation. e.g.

True.  I think my wording almost allows those, too, but it needs to use
something like "lifetime expires" instead of "block exits".

> (Note that in same way that the (N)RVO can cause unexpected behaviour,
> so can the above optimisation, so ideally it too should not be applied
> in unsafe cases such as the MV++ example.)

Ideally unsafe code such as MV++ should not be written.

Next try:

15Whenever a class object is copied and the original object and the copy
  have the same type, and the lifetime of the original object ends
  immediately* after the copy is created, an implementation is permitted to
  treat the original and the copy as two different ways of referring to the
  same object and not perform a copy at all.  In that case, the object is
  destroyed when the copy would have been destroyed without the
  optimization.

* For the purposes of this rule, implicit destructor calls are not
  considered in the lifetime of an object.

Jason
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/22
Raw View
On 20 Jun 1997 10:19:52 PDT, Jason Merrill <jason@cygnus.com> wrote:

>>>>>> Brian Parker <bparker@gil.com.au> writes:
>
>> On 18 Jun 97 02:15:53 GMT, Jason Merrill <jason@cygnus.com> wrote:
>
>>> In this testcase, the problem occurs because even though 'v' is not
>>> directly used after the call, references to data it controls are.  This
>>> would not be a problem if 'v' went out of scope immediately after the
>>> call, because any references to its data are undefined after it goes out of
>>> scope.
>
>> Unless some garbage collection scheme is used such that some of the
>> object's dynamically-allocated memory is retained beyond the local
>> scope (or if the object uses some global memory in its implementation-
>> see the MV++ example)
>
>I'm talking about a basic principle here: when the life of an object
>expires, references to its contents are undefined.  A garbage-collected
>object is not 'automatic', so it doesn't matter to the issue at hand.
>Let's talk about this version of C++.
>...
>In summary, garbage collection is irrelevant to this discussion.

The case I was thinking of was where there is a local variable with
internal dynamically-allocated memory that is garbage-collected. For
example, if one links in a garbage collector (which typically links in
a null global delete), then memory can be retained from STL vectors
thus-

vector<int> func(iterator& i)
{
        vector<int> v(10);
        i = v.begin();  // internal dynamic memory will be retained
    //  beyond local scope
       return v;
}

iterator i;
vector v2 = func(i);
*i = 2;   // incorrectly modifies v2 if NRVO applied.

This is a contrived example, but I could conceive of similar code
being written if garbage collection was widely used.

It could be argued that retaining such memory beyond the life of the
original object is bad design- in effect using garbage-collection to
hide a bug- and I wouldn't disagree with that.

The main purpose of my examples that fail under the (N)RVO was mainly
to indicate that such optimisations are not (as many seemed to assume)
completely benign (even when excluding unrelated side-effects in
copy-constructors and destructors) and that they can alter the
semantics of otherwise legal code sequences. With an awareness of
that, if, as is the case, the consensus is that such code sequences
*should* be undefined as they step outside of the design assumptions
of C++, then that's a reasonable approach.
As always my main concern is the pass-by-value problem.

>
>> Just some further comments on the actual wording-
>
>> (1) It should be limited to copies using the copy constructor only-
>> copy assignment operator calls are never implicitly created by the
>> compiler and hence shouldn't be elidable.
>
>Hmm...I had assumed that 12.8/15 already only applied to the copy
>constructor, but I guess I was wrong.  I don't see any compelling reason
>for it to apply to the copy assignment operator, so OK.
>
>> (2) By my reading, 12.8/15 is also used to elide copies of tempoaries
>> on initialisation. e.g.
>
>True.  I think my wording almost allows those, too, but it needs to use
>something like "lifetime expires" instead of "block exits".

Actually, the draft is very unclear as to whether *all* temporary
elision is described by 12.8/15.
[exp.call], [stmt.return] and [dcl.init/14] all defer to 12.2 to allow
temporary elision in these cases. 12.2, however, doesn't actually
describe temporary elision except for a non-normative example, and so
my assessment is that 12.2 itself implicitly refers to 12.8/15.

IMO, this should be clarified, preferably by moving the example from
12.2 to 12.8/15 and changing the various references to 12.2 to be to
12.8/15. Alternatively, 12.2 could have an explicit note added that
"temporaries may only be elided as per 12.8/15".

>...
>15Whenever a class object is copied and the original object and the copy
>  have the same type, and the lifetime of the original object ends
>  immediately* after the copy is created, an implementation is permitted to
>  treat the original and the copy as two different ways of referring to the
>  same object and not perform a copy at all.  In that case, the object is
>  destroyed when the copy would have been destroyed without the
>  optimization.
>
>* For the purposes of this rule, implicit destructor calls are not
>  considered in the lifetime of an object.

Yes, that would seem to be close to what would be required.

As per (1) above, I would change it to -
"15 Whenever a class object is copied (via a copy-constructor) ...."

The note might be better incorporated into the paragraph as something
like-

"... and the lifetime of the original object ends immediately
(excepting implicit destructor calls) after the copy is created,..."

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/22
Raw View
On 20 Jun 1997 10:19:14 PDT, jpotter@falcon.lhup.edu (John Potter)
wrote:

>On 19 Jun 1997 09:06:42 PDT, bparker@gil.com.au (Brian Parker) wrote:
>
>: However, as I have shown elsewhere in this thread, the same issues
>: arise with the NRVO and RVO, albeit in a much more limited way.
>
>Just trying to clear your terms.
> T f (T t) {
>  T tmp(t);
>  return tmp;
>  }
>Removing tmp and constructing the copy in the return value is called
>"return value optimization" in D&E and MEC++.  It is called "named
>return value optimization" by Lippman in IC++OM and C++Report.  Since
Yes, I call this the NRVO.

>you are using both terms, I assume that they mean two different things
>to you.
> T f (T t) {
>  return T(t);
>  }
>I think that you are calling removal of the unnamed temporary from the
>return RVO and the one above NRVO.
Yes, I call this the RVO.
It appears that my usage is, confusingly, different to the general
usage of these terms- I'll have to start using different names (but
what is a good name for the second optimisation?).

BTW, it seems to me that MEC++ item 20 also refers to the second
example as the RVO; I think that is where I got the term from.

> I think that these are two
>different issues and the former requires 12.8/15 while the latter
>requires only 12.2/1.  Could you clarify your terms?  I think that you
>asked for clarification of that and other unnamed temp removals and
>maybe someone else will address that.

I have always treated them as essentially the same issue; in terms of
my examples demonstrating altered semantics on temporary elision,
eliding temps via the NRVO, RVO (as I use the term), or on variable
initialisation are all equivalent and so the standard should either
allow temp elision via all of them or none of them (although as a
pragmatic issue, the NRVO is the most likely to lead to unexpected
results in practice).

(Note: even if the standard didn't make explicit allowance for these
optimisations, my expectation would be that they would have been made
available via the compiler vendors anyway- just with a switch
available to disable them, and indeed for the many important cases
with bitwise copy semantics (i.e. implicit copy constructor and
destructor) the optimisations can always be done via the "as-if" rule.
I agree that they are all important optimisations.)

By my reading of the draft, I have always presumed that all temporary
elision is allowed for *only* by 12.8/15 (including the NRVO, RVO, and
on variable initialisation). I agree that the draft is vague on this
issue- elsewhere it refers to 12.2 for license to elide temps, but
12.2 itself has nothing to say on the issue except give an example.
Hence, my interpretation is that the example in 12.2 is itself relying
upon 12.8/15.

Certainly, this needs to be clarified if 12.8/15 is going to be
modified.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/15
Raw View
In a recent thread, the virtues and dangers of the named return value
optimisation were discussed.
I gave a contrived example which would fail if the named return value
optimisation was applied, but the general concensus was that the
example given was badly designed as it confused value and reference
semantics and hence was of no practical significance.

I have since noticed an existing well-known matrix class library which
would fail in a similar (more plausible) example.

The example follows-

In the MV++ matrix library (v 1.5 a) there is a matrix class
MV_Vector_double class which is a vector with value (copy) semantics
e.g.

MV_Vector_double A(10);  // length 10 vector

MV_Vector_double B(A);   // copy of A

MV_Vector_double C;
C = A;   // copy of A

One can, however, initialise it as a view on an existing C array, so
that it can be easily used with C code, thus-

double a[] = {1.0, 2.0, 3.0};

MV_Vector_double D(a, 3, MV_Vector::ref);
D[1] = 4.0; // set a[1] = 4.0;

Note that this mixing of reference semantics on construction with
value semantics on copying is, I think, a reasonable design choice-
this is similar to the built-in references.

Now we can write a useful function to initialise a vector with a copy
of a C array-

double a[] = {1.0, 2.0, 3.0};

MV_Vector_double Copy_C_array()
{
 MV_Vector_double A(a, 3, MV_Vector::ref);
 return A;
}

MV_Vector_double B = Copy_C_array();
B[1] = 4.0; // a[1] == 2.0 as B is a copy

However, if the NRVO is enabled then B will incorrectly be a reference
to a and hence a[1] == 4.0.

Note that the same problem occurs with the return value optimisation
i.e. if Copy_C_array() was written as-

MV_Vector_double Copy_C_array()
{
 return MV_Vector_double(a, 3, MV_Vector::ref);
}

This example is of the same form as my previously posted example, but
it is interesting as it occurs using a well-known matrix package.
Whilst I wouldn't mind such code changes if I had explicitly enabled a
non-standard compiler optimisation, I still find it unsettling that
such non-deterministic behaviour is allowed by the standard.

Although the draft otherwise only guarantees that values are copied
one or more times on pass-by-value or return-by-value, that is a
benign form of non-determinism compared with the semantics-altering
behaviour of zero or more copies that [class.copy] paragraph 15
introduces.

Any comments on this example?

Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: David Vandevoorde <daveed@vandevoorde.com>
Date: 1997/06/15
Raw View
Brian Parker wrote:
[...]
> MV_Vector_double Copy_C_array()
> {
>         MV_Vector_double A(a, 3, MV_Vector::ref);
>         return A;
> }

Writing a copy-function this way is asking for trouble:
there is no copy-function visible in this function (and
the standard does not mandate an invisible one).

[...]
> Note that the same problem occurs with the return value optimisation
> i.e. if Copy_C_array() was written as-
>
> MV_Vector_double Copy_C_array()
> {
>         return MV_Vector_double(a, 3, MV_Vector::ref);
> }

Same thing: you never requested a copy of your temporary
object, so you cannot reasonably expect one on every
implementation. If you want a copy, ask for one:

 return MV_Vector_double(
           MV_Vector_double(a, 3, MV_Vector::ref));

> This example is of the same form as my previously posted example, but
> it is interesting as it occurs using a well-known matrix package.

Well yes, but I'm sure you won't argue that the example
function you posted is not reasonable? The problem is
not with the MV++ package per se.

> Although the draft otherwise only guarantees that values are copied
> one or more times on pass-by-value or return-by-value, that is a
  ^^^
Where does it guarantee this? [stmt.return] certainly doesn't,
and [expr.call] makes an explicit caveat that the copy may be
folded into a conversion operation.

> benign form of non-determinism compared with the semantics-altering
> behaviour of zero or more copies that [class.copy] paragraph 15
> introduces.

``By value'' means that a value as opposed to a reference is
used to cross a call-boundary. It does not mean that a copy
must be made using a copy-ctor for that  purpose (otherwise,
it would probably have been called ``by copy'').

C++ offers the less deterministic mechanism to those situations
that can deal with it (and reap performance benefits as a
consequence), and a simple syntax to enforce a ``by copy''
approach when necessary (or convenient).

 Daveed
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Pete Becker <petebecker@acm.org>
Date: 1997/06/15
Raw View
Brian Parker wrote:
>
> In a recent thread, the virtues and dangers of the named return value
> optimisation were discussed.
> I gave a contrived example which would fail if the named return value
> optimisation was applied, but the general concensus was that the
> example given was badly designed as it confused value and reference
> semantics and hence was of no practical significance.
It's nothing as subtle as confusing value and reference semantics. Don't
try to make this tricky. It's easy. See below.

>
> I have since noticed an existing well-known matrix class library which
> would fail in a similar (more plausible) example.
>
> The example follows-
>
> In the MV++ matrix library (v 1.5 a) there is a matrix class
> MV_Vector_double class which is a vector with value (copy) semantics
> e.g.
>
> MV_Vector_double A(10);  // length 10 vector
>
> MV_Vector_double B(A);   // copy of A
>
> MV_Vector_double C;
> C = A;                  // copy of A
>
> One can, however, initialise it as a view on an existing C array, so
> that it can be easily used with C code, thus-
>
> double a[] = {1.0, 2.0, 3.0};
>
> MV_Vector_double D(a, 3, MV_Vector::ref);
> D[1] = 4.0;     // set a[1] = 4.0;
>
> Note that this mixing of reference semantics on construction with
> value semantics on copying is, I think, a reasonable design choice-
> this is similar to the built-in references.
>
> Now we can write a useful function to initialise a vector with a copy
> of a C array-
>
> double a[] = {1.0, 2.0, 3.0};
>
> MV_Vector_double Copy_C_array()
> {
>         MV_Vector_double A(a, 3, MV_Vector::ref);
At this point, presumably, A holds a pointer to the array 'a', if I've
got this right.


>         return A;
And at this point, invoking the copy constructor would copy the array
'a' into a distinct array, and put a pointer to that array into the copy
of A. Right?

> }
>
> MV_Vector_double B = Copy_C_array();
> B[1] = 4.0; // a[1] == 2.0 as B is a copy
>
> However, if the NRVO is enabled then B will incorrectly be a reference
> to a and hence a[1] == 4.0.
Yes, if the copy construction were skipped then B would hold a pointer
to 'a' instead of holding a pointer to a distinct array. Have I got this
right?
 Skipping the copy constructor is not legal. The object A consists of
the storage allocated for A itself and the array 'a'. Since the array
'a' is being accessed later (in order to see what happens to a[1]), the
compiler is not permitted to eliminate the copy constructor.
 The problem in the analysis of this issue lies in thinking that an
"object" is only the members that we name. This is not correct. It is
the region of memory that holds the data. All of the data, not part of
it.
 -- Pete
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: fjh@mundook.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/16
Raw View
David Vandevoorde <daveed@vandevoorde.com> writes:

>Brian Parker wrote:
>[...]
>> MV_Vector_double Copy_C_array()
>> {
>>         MV_Vector_double A(a, 3, MV_Vector::ref);
>>         return A;
>> }
>
>Writing a copy-function this way is asking for trouble:
>there is no copy-function visible in this function (and
>the standard does not mandate an invisible one).
...
>If you want a copy, ask for one:
>
> return MV_Vector_double(
>           MV_Vector_double(a, 3, MV_Vector::ref));

Close, but no cigar.  You may think you have asked for a copy, but the
implementation can _still_ optimize all the copies away!

The reason is that same paragraph, 12.8/15:

 | WHENEVER a class object is copied and the original object and the copy
 | have  the  same  type, if the implementation can prove that either the
 | original object or the copy will never again be  used  except  as  the
 | result  of  an implicit destructor call (_class.dtor_), an implementa-
 | tion is permitted to treat the original and the copy as two  different
 | ways  of  referring  to the same object and not perform a copy at all.

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/16
Raw View
On 15 Jun 1997 19:47:49 PDT, Pete Becker <petebecker@acm.org> wrote:
>...
>Yes, if the copy construction were skipped then B would hold a pointer
>to 'a' instead of holding a pointer to a distinct array. Have I got this
>right?
> Skipping the copy constructor is not legal. The object A consists of
>the storage allocated for A itself and the array 'a'. Since the array
>'a' is being accessed later (in order to see what happens to a[1]), the
>compiler is not permitted to eliminate the copy constructor.
> The problem in the analysis of this issue lies in thinking that an
>"object" is only the members that we name. This is not correct. It is
>the region of memory that holds the data. All of the data, not part of
>it.
> -- Pete

I agree with all of the above, and if compilers really were smart
enough not to apply the NRVO or RVO when *any* part of the object was
used again (including indirectly accessed memory) then I would have no
concerns. However, compilers cannot, in general, check this- the
question of what memory belongs to an object's definition depends upon
the class designer and can be made arbitrarily difficult for a
compiler to deduce, (e.g. rather than storing a pointer directly, the
class designer may store an integer index into a global pointer lookup
table).

In fact, note that both Borland 5.01 and VC++ 5.0 do not detect the
indirect use of memory in the above example and blindly apply the
return value optimisation giving the incorrect result (neither
BC++5.01 or VC++ 5.0 currently support the NRVO). Following this
posting is a simplified code example that demonstrates this.

So to paraphrase your comments, [class.copy] paragraph 15 should be
interpreted with a liberal meaning of "object" that includes *all*
resources used by the object, and as that is, in general, not
computable, then compilers are not allowed to apply the NRVO or RVO
except in trivial cases (or, of course, via some non-standard compiler
switch).

I would be happy with the draft in that case- provided all compiler
vendors had the same interpretation of [class.copy] para 15 (and
preferably a note was added to the draft to clarify this); however, I
suspect that this interpretation will not be universally held.

---8<-------------------------------------------------------------------------------

#include <iostream.h>
#include <string.h>

class Vec {
public:

 Vec() : p(new int[10]), fOwner(true) {}
 Vec(int* pIn) : p(pIn), fOwner(false) {}

   Vec(const Vec& v) : p(new int[10]), fOwner(true) {memcpy(p,
v.p, 10*sizeof(int));cout << "cc\n";}
 ~Vec() {if (fOwner) delete[] p;}

 int& operator[](int n) {return p[n];}

 bool fOwner;
 int* p;
};

Vec InitVector(int* p)
{
#if 0
 // named return value optimisation test
 Vec temp(p);
    return temp;
#else
 // return value optimisation test
             // This should, of course, have identical semantics
             //  to the above
 return Vec(p);
#endif
}

int main()
{
 int c_array[5] = {1,2,3,4,5};

 Vec v = InitVector(c_array);

 v[2] = 10;

 if (c_array[2] == 10) {
  cout << "Buggy implementation\n";
 } else {
  cout << "Implementation OK\n";
 }
   char c;
   cin.get(c);

 return 0;
}

----8<-----------------------------------------------------------------------
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/16
Raw View
On 15 Jun 1997 19:47:01 PDT, David Vandevoorde
<daveed@vandevoorde.com> wrote:

>Brian Parker wrote:
>[...]
>> MV_Vector_double Copy_C_array()
>> {
>>         MV_Vector_double A(a, 3, MV_Vector::ref);
>>         return A;
>> }
>
>Writing a copy-function this way is asking for trouble:
>there is no copy-function visible in this function (and
>the standard does not mandate an invisible one).
>
>[...]
>> Note that the same problem occurs with the return value optimisation
>> i.e. if Copy_C_array() was written as-
>>
>> MV_Vector_double Copy_C_array()
>> {
>>         return MV_Vector_double(a, 3, MV_Vector::ref);
>> }
>
>Same thing: you never requested a copy of your temporary
>object, so you cannot reasonably expect one on every
>implementation. If you want a copy, ask for one:
>
> return MV_Vector_double(
>           MV_Vector_double(a, 3, MV_Vector::ref));
>
>> This example is of the same form as my previously posted example, but
>> it is interesting as it occurs using a well-known matrix package.
>
>Well yes, but I'm sure you won't argue that the example
>function you posted is not reasonable? The problem is
>not with the MV++ package per se.
>
>> Although the draft otherwise only guarantees that values are copied
>> one or more times on pass-by-value or return-by-value, that is a
>  ^^^
>Where does it guarantee this? [stmt.return] certainly doesn't,
>and [expr.call] makes an explicit caveat that the copy may be
>folded into a conversion operation.
>
>> benign form of non-determinism compared with the semantics-altering
>> behaviour of zero or more copies that [class.copy] paragraph 15
>> introduces.
>
>``By value'' means that a value as opposed to a reference is
>used to cross a call-boundary. It does not mean that a copy
>must be made using a copy-ctor for that  purpose (otherwise,
>it would probably have been called ``by copy'').
>
>C++ offers the less deterministic mechanism to those situations
>that can deal with it (and reap performance benefits as a
>consequence), and a simple syntax to enforce a ``by copy''
>approach when necessary (or convenient).
>
> Daveed

I would disagree with you here- I think that the semantics of
pass-by-value or return-by-value *does* require a true copy to be
returned. What you would call pass-by-copy is exactly what I have
always understood by pass-by-value semantics.

Allowing return-by-value not to make a true copy via the copy
constructor is semantically equivalent to returning a bit-wise copy,
and so if for-

// return an STL length 3 vector
vector<int> Get_array()
{
 vector<int> A(3);
 return A;
}

vector<int> B = Copy_array();

the implementation is allowed to store a bitwise copy of A in B (or,
equivalently, to construct A directly in B) then by that same
argument, for-

vector<int> A(3);  // global

vector<int> Copy_array()
{
 return A;
}

vector<int> B = Copy_array();

the implementation is again allowed to save a bitwise copy of A in B,
and hence the global A and B share the dynamically-allocated memory of
A between them, and hence vector<int> cannot safely be treated as a
simple value type, as is intended.

Note that I am only arguing that return-by-value must act "as if" a
true copy was made- if an implementation really could prove that a
returned variable was never used again (which is always the case for
local variables) *and* that no part of the variable's
dynamically-allocated memory or other resouces was ever used again,
then the copy could be elided (but of course, a compiler could almost
never determine this).

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Pete Becker <petebecker@acm.org>
Date: 1997/06/16
Raw View
Brian Parker wrote:
 >
 > On 15 Jun 1997 19:47:49 PDT, Pete Becker <petebecker@acm.org> wrote:
 > >...
 > >Yes, if the copy construction were skipped then B would hold a pointer
 > >to 'a' instead of holding a pointer to a distinct array. Have I got this
 > >right?
 > >       Skipping the copy constructor is not legal. The object A consists of
 > >the storage allocated for A itself and the array 'a'. Since the array
 > >'a' is being accessed later (in order to see what happens to a[1]), the
 > >compiler is not permitted to eliminate the copy constructor.
 > >       The problem in the analysis of this issue lies in thinking that an
 > >"object" is only the members that we name. This is not correct. It is
 > >the region of memory that holds the data. All of the data, not part of
 > >it.
 > >       -- Pete
 >
 > I agree with all of the above, and if compilers really were smart
 > enough not to apply the NRVO or RVO when *any* part of the object was
 > used again (including indirectly accessed memory) then I would have no
 > concerns. However, compilers cannot, in general, check this- the
 > question of what memory belongs to an object's definition depends upon
 > the class designer and can be made arbitrarily difficult for a
 > compiler to deduce, (e.g. rather than storing a pointer directly, the
 > class designer may store an integer index into a global pointer lookup
 > table).
That's why the rule says you can't optimize away the copy constructor
unless you can prove that it's harmless. That doesn't take any smarts at
all: just don't do it. Smarter compilers will recognize situations in
which it is OK, and take advantage of them.

 >
 > In fact, note that both Borland 5.01 and VC++ 5.0 do not detect the
 > indirect use of memory in the above example and blindly apply the
 > return value optimisation giving the incorrect result (neither
 > BC++5.01 or VC++ 5.0 currently support the NRVO). Following this
 > posting is a simplified code example that demonstrates this.
 >
 > So to paraphrase your comments, [class.copy] paragraph 15 should be
 > interpreted with a liberal meaning of "object" that includes *all*
 > resources used by the object, and as that is, in general, not
 > computable, then compilers are not allowed to apply the NRVO or RVO
 > except in trivial cases (or, of course, via some non-standard compiler
 > switch).
Without the conclusory language, that's what the rule says: if you can't
prove it's safe you can't do it. Whether this results in only being able
to apply this optimization to "trivial cases" depends on your definition
of trivial. There are many classes for which this can be done easily.

 >
 > I would be happy with the draft in that case- provided all compiler
 > vendors had the same interpretation of [class.copy] para 15 (and
 > preferably a note was added to the draft to clarify this); however, I
 > suspect that this interpretation will not be universally held.

Don't mistake incorrect implementation for misinterpretation. Compiler
implementors sometimes overlook edge cases, and need to have them
pointed out. If, after you point it out, a vendor insisted that their
compiler was correct they would be misinterpreting the standard. Or
elevating marketing over correctness.
 On the other hand, if you think this rule isn't worded properly, what
do you propose as an alternative?
 -- Pete
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: fjh@mundook.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/16
Raw View
Pete Becker <petebecker@acm.org> writes:

>Yes, if the copy construction were skipped then B would hold a pointer
>to 'a' instead of holding a pointer to a distinct array. Have I got this
>right?

Yep.

> Skipping the copy constructor is not legal.  The object A consists of
>the storage allocated for A itself and the array 'a'. Since the array
>'a' is being accessed later (in order to see what happens to a[1]), the
>compiler is not permitted to eliminate the copy constructor.

I strongly disagree.  Brian Parker's interpretation of the draft is
correct: the object `A' consists of the storage allocated for `A' itself.
It does not include the array `a'.  The DWP is quite clear about this, IMHO.

> The problem in the analysis of this issue lies in thinking that an
>"object" is only the members that we name. This is not correct. It is
>the region of memory that holds the data. All of the data, not part of
>it.

No, I disagree.  Your interpretation here is completely untenable, IMHO.

Firstly, it is not well-defined.  What exactly do you mean by
"all of the data"?  Do you mean all the data in the entire program?
If not, then what exactly?

As far as I can tell, your definition of "object" is an unformalizable
"Humpty-Dumpty" definition: an object consists of what you want want it
to consist of, no more and no less.

Secondly, applying this interpretation of object to the rest of the DWP
would cause havoc.  For example, consider applying your definition of
object to 7.1.5.1/4:

|  7.1.5.1  The cv-qualifiers                               [dcl.type.cv]
...
|  4. ... any attempt to modify a const object during its lifetime
| results in undefined behaviour.

Now take the following example:

 struct foo {
  int *p;
  foo(int *pp) : p(pp) {}
 };
 int i;
 const foo f(&i);
 i++;

Is `i' a part of the const object `f'?
If so (as I would exect, assuming your definition of "object"),
then the behaviour is undefined.  But that is surely not the
intent of the committee, and would undoubtably lots of code.

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Jason Merrill <jason@cygnus.com>
Date: 1997/06/16
Raw View
>>>>> Pete Becker <petebecker@acm.org> writes:

>  Skipping the copy constructor is not legal. The object A consists of
> the storage allocated for A itself and the array 'a'.

That's an unusual definition of 'object', which does not match the
definition in [intro.object].

I thought we discussed this before and decided that 12.8 was referring to
the actual object itself, not the object and everything it points to.  So
the example would produce surprising results in the presence of the RVO.

But I'm OK with that.  I'm comfortable saying that playing games with
references and values in the same class, and relying on the copy
constructor to switch between them, is asking for trouble.

Jason
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/06/16
Raw View
On 16 Jun 97 08:35:23 GMT, bparker@gil.com.au (Brian Parker) wrote:

: In fact, note that both Borland 5.01 and VC++ 5.0 do not detect the
: indirect use of memory in the above example and blindly apply the
: return value optimisation giving the incorrect result (neither
: BC++5.01 or VC++ 5.0 currently support the NRVO). Following this
: posting is a simplified code example that demonstrates this.

We have yet another example which has nothing to do with return value
optimization.  The temporary which is removed is in main.  It is
compiler generated and can be removed.  I give two other examples
which produce the same results "buggy".


: int main()
: {
:  int c_array[5] = {1,2,3,4,5};

Case 1:
:  Vec v = InitVector(c_array);

Case 2:
 Vec v2 = c_array;
Case 3:
 Vec v3 = Vec(c_array);

:  v[2] = 10;

:  if (c_array[2] == 10) {
:   cout << "Buggy implementation\n";
:  } else {
:   cout << "Implementation OK\n";
:  }
:    char c;
:    cin.get(c);

:  return 0;
: }

Case 3 translation:
 Vec temp3(c_array);
 Vec v3(temp3);
Optimize out temp3 to get
 Vec v3(c_array);

Case 2 translation:
 Vec temp2(c_array);
 Vec v2(c_array);
Optimize out temp2
 Vec v2(c_array);

Case 1 translation:
Functions which return a compound type by value are often implemented
as a function returning void with a pointer to uninitialized space for
the return value.
 Vec temp1space;  // not constructed
 InitVector(&temp1space, c_array);
 Vec v(temp1space);
Optimize out temp1space giving
 Vec v;  // not constructed
 InitVector(&v, c_array);

InitVector translation:
void InitVector(Vec* space, int* p) {
 Vec temp4(p);
 copyConstruct(space, temp4);
 }
Optimize out temp4
 construct(space, p);

In all cases, the removed item was an unnamed compiler generated
temporary.  The removal is allowed by 12.2 and does not require the
use of 12.8.  The class is broken.  One could also question the
validity of an operator[] which allows modification of an
implementation which it does not own.  Not allowing the use of 12.8 in
the unused version of InitVector would permit the broken class to
appear to be working properly.  Any code which depends upon copying an
unnamed temporary is poorly designed.  Restricting the use of 12.8 for
return value optimization would provide a way to code functions which
allow broken classes to operate, at the expense of preventing
temporary removal in functions using correct classes.  Use of the
broken classes can be coded as construction followed by assignment
without changing the draft.

John
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: fjh@murlibobo.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/16
Raw View
Pete Becker <petebecker@acm.org> writes:

>Brian Parker wrote:
> >
> > ... compilers cannot, in general, check this- the
> > question of what memory belongs to an object's definition depends upon
> > the class designer and can be made arbitrarily difficult for a
> > compiler to deduce, (e.g. rather than storing a pointer directly, the
> > class designer may store an integer index into a global pointer lookup
> > table).
>That's why the rule says you can't optimize away the copy constructor
>unless you can prove that it's harmless. That doesn't take any smarts at
>all: just don't do it. Smarter compilers will recognize situations in
>which it is OK, and take advantage of them.

Could you give us an example where a compiler could take advantage
of this rule (12.8[class.copy]/15), assuming your interpretation,
but where the compiler couldn't already perform the same optimization
by the "as-if" rule?

> On the other hand, if you think this rule isn't worded properly, what
>do you propose as an alternative?

I think the rule _is_ worded clearly, and I think that you are the one
misinterpreting it.  You can't just invent a new definition of "object"
specifically for this one rule.  I understand why you don't like the
implications of interpreting the rule using the definition of "object"
that is used everywhere else in the draft, but that doesn't give us
licence to ignore the normal definition of "object".

If your interpretation were to be adopted, then as far as I can tell,
the rule would be at best useless and at worst meaningless; I don't
think there is any example where a compiler could use the rule to
perform optimizations that could not be just as easily performed
using only the "as-if" rule.  If that were the case, then the rule
should be deleted.

All this is not to say that deleting that rule would necessarily be
a bad thing, just that it would be a change from the status quo.

I see three alternatives:

 1a.  Leave the rule in there, but clarify that "object"
      means "Humpty-Dumpty object" for the purposes of this
      rule.
 1b.  Delete the rule.  [This is equivalent to 1a, but simpler.]

      Disadvantage: potential loss of efficiency for many programs.

 2.   Leave the rule in there.  (And clarify, if really necessary,
      that "object" in this rule means the same thing that it does
      in all the other rules in the draft.)

      Disadvantage: potentially breaks some programs.

 3.   Try to come up with some other rule instead.

      Disadvantage: too difficult to find a workable rule.
      May be difficult to gain concensus.  Potential delay of the
      standard, or loss of the opportunity to fix other problems.

None of these are particularly appealing, so I don't have a strong
opinion as to which is the best alternative.  Given the timing,
it might be best to stick with the status quo.

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: David Vandevoorde <daveed@vandevoorde.com>
Date: 1997/06/17
Raw View
Fergus Henderson wrote:
>
> David Vandevoorde <daveed@vandevoorde.com> writes:
[...]
> >If you want a copy, ask for one:
> >
> >       return MV_Vector_double(
> >                 MV_Vector_double(a, 3, MV_Vector::ref));
>
> Close, but no cigar.  You may think you have asked for a copy, but the
> implementation can _still_ optimize all the copies away!

Ah, you're right. I was in wishful thinking mode.

I suppose that this makes me agree with the fact that paragraph
12.8/15 needs tightening up.

 Daveed
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Pete Becker <petebecker@acm.org>
Date: 1997/06/17
Raw View
Fergus Henderson wrote:
 >
 > Pete Becker <petebecker@acm.org> writes:
 >
 > >Brian Parker wrote:
 > > >
 > > > ... compilers cannot, in general, check this- the
 > > > question of what memory belongs to an object's definition depends upon
 > > > the class designer and can be made arbitrarily difficult for a
 > > > compiler to deduce, (e.g. rather than storing a pointer directly, the
 > > > class designer may store an integer index into a global pointer lookup
 > > > table).
 > >That's why the rule says you can't optimize away the copy constructor
 > >unless you can prove that it's harmless. That doesn't take any smarts at
 > >all: just don't do it. Smarter compilers will recognize situations in
 > >which it is OK, and take advantage of them.
 >
 > Could you give us an example where a compiler could take advantage
 > of this rule (12.8[class.copy]/15), assuming your interpretation,
 > but where the compiler couldn't already perform the same optimization
 > by the "as-if" rule?

#include <iostream>

class C
{
public:
 static int destructor_count;
 ~C() { destructor_count++; }
};

int C::destructor_count = 0;

void f( C c )
{
}

int main()
{
 C c;
 f(c);
 if( C::destructor_count != 0 )
  cout << "Already got a destructor call\n";
 return 0;
}

 >
 > >       On the other hand, if you think this rule isn't worded properly, what
 > >do you propose as an alternative?
 >
 > I think the rule _is_ worded clearly, and I think that you are the one
 > misinterpreting it.  You can't just invent a new definition of "object"
 > specifically for this one rule.  I understand why you don't like the
 > implications of interpreting the rule using the definition of "object"
 > that is used everywhere else in the draft, but that doesn't give us
 > licence to ignore the normal definition of "object".
 >
 > If your interpretation were to be adopted, then as far as I can tell,
 > the rule would be at best useless and at worst meaningless; I don't
 > think there is any example where a compiler could use the rule to
 > perform optimizations that could not be just as easily performed
 > using only the "as-if" rule.  If that were the case, then the rule
 > should be deleted.
Then think harder.

 >
 > All this is not to say that deleting that rule would necessarily be
 > a bad thing, just that it would be a change from the status quo.
 >
 > I see three alternatives:
 >
 >         1a.  Leave the rule in there, but clarify that "object"
 >              means "Humpty-Dumpty object" for the purposes of this
 >              rule.

Sigh. More ad hominem argument. I've stopped reading.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/06/17
Raw View
On 16 Jun 97 13:45:24 GMT, bparker@gil.com.au (Brian Parker) wrote:

: Allowing return-by-value not to make a true copy via the copy
: constructor is semantically equivalent to returning a bit-wise copy,
: and so if for-

: // return an STL length 3 vector
: vector<int> Get_array()
: {
:  vector<int> A(3);
:  return A;
: }
: vector<int> B = Copy_array();

: the implementation is allowed to store a bitwise copy of A in B (or,
: equivalently, to construct A directly in B)

Effectively, it removes the named variable A and uses the return value
area which happens to be B due to an optimization in the caller code.
Since A does not exist, neither of the above statements is strictly
correct.

: then by that same
: argument, for-

: vector<int> A(3);  // global
: vector<int> Copy_array()
: {
:  return A;
: }
: vector<int> B = Copy_array();

: the implementation is again allowed to save a bitwise copy of A in B,
: and hence the global A and B share the dynamically-allocated memory of
: A between them, and hence vector<int> cannot safely be treated as a
: simple value type, as is intended.

This is just plain wrong.  A, B and Copy_array have external linkage
and there is no way that the implementation can show that either A or
B is not used again or that this is the only use of Copy_array.  It
certainly has nothing to do with (N)RVO.

: Note that I am only arguing that return-by-value must act "as if" a
: true copy was made- if an implementation really could prove that a
: returned variable was never used again (which is always the case for
: local variables) *and* that no part of the variable's
: dynamically-allocated memory or other resouces was ever used again,
: then the copy could be elided (but of course, a compiler could almost
: never determine this).

As you know, I do not agree with your desires.  An implementation
should be allowed to assume that a copy ctor does not have any wierd
side effects which is true for most copy ctors.  See Jason Merrill's
post.  My example:

template <class T>
T operator+ (T const& lhs, T const& rhs) {
 T temp(lhs);
 temp += rhs;  // member of T
 return temp;
 }

I would very much like temp to be removed, which is the (N)RVO.  RVO
is the term used in D&E, USL C++ documentation and MEC++.  NRVO is the
term used in IC++OM which seems to be catching on.  It has been
suggested that the g++ extension should be called NRVO.

In any case, there seems to be little desire to change the draft.
Jason Merril has posted that g++2.8 will not include the (N)RVO and
will continue the extension.  I have learned that one major vendor has
implemented (N)RVO as an option with the default being not enabled.
In the short term, it seems that the market is going in the direction
that you would like.  Only time will tell whether the (N)RVO is
desired by most users.  In the interim, you will need to assume that
it is implemented and not write code which must be portable which
would break under (N)RVO.

John
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/17
Raw View
On 16 Jun 97 16:54:02 GMT, Pete Becker <petebecker@acm.org> wrote:
>...
>Without the conclusory language, that's what the rule says: if you can't
>prove it's safe you can't do it. Whether this results in only being able
>to apply this optimization to "trivial cases" depends on your definition
>of trivial. There are many classes for which this can be done easily.

As far as I can see, 12.8/15 could only be unambiguously safely
applied by a compiler for a class with bitwise copy semantics, i.e.
either the class and all its members and base classes have implicitly
defined copy constructors, or any user-provided explicit copy
constructors must involve only simple assignment of members. In these
cases, though, the copy could already be elided by the "as if" rule.

Possibly, if the explicit copy constructor also had some simple benign
side-effect unrelated to copying such as outputting a debug message,
then a smart compiler might also be able to elide that using 12.8/5,
but I don't think that would be a useful additional optimisation given
that the class designer could simply remove the extraneous code
himself.

>
> >
> > I would be happy with the draft in that case- provided all compiler
> > vendors had the same interpretation of [class.copy] para 15 (and
> > preferably a note was added to the draft to clarify this); however, I
> > suspect that this interpretation will not be universally held.
>
>Don't mistake incorrect implementation for misinterpretation. Compiler
>implementors sometimes overlook edge cases, and need to have them
>pointed out. If, after you point it out, a vendor insisted that their
>compiler was correct they would be misinterpreting the standard. Or
>elevating marketing over correctness.
> On the other hand, if you think this rule isn't worded properly, what
>do you propose as an alternative?
> -- Pete

IMHO, the best alternative is to simply remove 12.8/5.
Compiler vendors could still provide the unsafe optimisations that
12.8/5 allows but they would be forced to provide a switch to disable
them.

At the very least, add some weasel wording to 12.8/5 to make it clear
that the meaning of "object is used" is as you have suggested (as this
thread has demonstrated, that interpretation is hardly unanimous).

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/17
Raw View
On 16 Jun 97 17:46:06 GMT, jpotter@falcon.lhup.edu (John Potter)
wrote:
>...
>We have yet another example which has nothing to do with return value
>optimization.  The temporary which is removed is in main.  It is
>compiler generated and can be removed.  I give two other examples
>which produce the same results "buggy".
>...
>Case 1:
>:  Vec v = InitVector(c_array);
>Case 2:
> Vec v2 = c_array;
>Case 3:
> Vec v3 = Vec(c_array);
>...
>
>Case 3 translation:
> Vec temp3(c_array);
> Vec v3(temp3);
>Optimize out temp3 to get
> Vec v3(c_array);
>
>Case 2 translation:
> Vec temp2(c_array);
> Vec v2(c_array);
>Optimize out temp2
> Vec v2(c_array);
>
>Case 1 translation:
>Functions which return a compound type by value are often implemented
>as a function returning void with a pointer to uninitialized space for
>the return value.
> Vec temp1space;  // not constructed
> InitVector(&temp1space, c_array);
> Vec v(temp1space);
>Optimize out temp1space giving
> Vec v;  // not constructed
> InitVector(&v, c_array);
>
>InitVector translation:
>void InitVector(Vec* space, int* p) {
> Vec temp4(p);
> copyConstruct(space, temp4);
> }
>Optimize out temp4
> construct(space, p);
>
>In all cases, the removed item was an unnamed compiler generated
>temporary.  The removal is allowed by 12.2 and does not require the
>use of 12.8.  The class is broken.  One could also question the
>validity of an operator[] which allows modification of an
>implementation which it does not own.  Not allowing the use of 12.8 in
>the unused version of InitVector would permit the broken class to
>appear to be working properly.  Any code which depends upon copying an
>unnamed temporary is poorly designed.

Excellent arguments as always; in fact they raise an issue that I
hadn't thought much about.

Your argument is essentially that in the same way that

Vec v3 = Vec(c_array);    // (1)

can be transformed by the compiler to

Vec v3(c_array);    // (2)

then the return value of InitVector can be optimised away.

I would agree with this argument, except that I don't think that even
this optimisation is allowed by 12.2, and so it too relies upon
12.8/15.

12.2/2 is just a non-normative example. The real description of which
temporaries can be elided without 12.8/15 is in 8.5.3 [dcl.init.ref]
which states that when binding an rvalue (temporary) to a const
reference then the compiler can either bind it directly or can make an
arbitrary number of copies before binding the final copy. It is only
these compiler-generated temps that can be "elided" without using
12.8/15, not the initial temporary being copied- I can't find any
allowance by the draft to elide the initial temporary in (1) except
via 12.8/15 (please correct me if I am wrong here).

(1) and (2) are, in fact, definitely not equivalent under 12.2 if e.g.
Vec has an inaccessible copy constructor, as 12.2 states that all
semantic restrictions must be honoured as if the copy was made.

So, if one interprets "object" in 12.8/15 to mean memory used both
directly and indirectly, a compiler *cannot* convert (1) to (2) in
cases such as my example- in any case, it is relying upon 12.8/15.

>  Restricting the use of 12.8 for
>return value optimization would provide a way to code functions which
>allow broken classes to operate, at the expense of preventing
>temporary removal in functions using correct classes.  Use of the
>broken classes can be coded as construction followed by assignment
>without changing the draft.

(actually, the way 12.8/15 is written it can elide *any* copy
including those done via a copy assignment operator, but that is just
an editorial problem)

Let me make it clear that I don't want 12.8/15 restricted *just* to
prevent the return value optimisation. In fact my third preferred
option would be to restrict 12.8/15 to apply *only* to initialisations
and, therefore, the RVO. As I have previously argued, I think that
allowing 12.8/5 to elide copies on pass-by-value is the main flaw and
I think most people have underestimated the danger in destroying the
fundamental axiom that pass-by-value can't alter its argument. The
NRVO and RVO only occur in a much more circumscribed situation- i.e.
initialisations- and so the problems can be worked around provided one
is aware of them (by, as you suggest, using default construction
followed by assignment rather than direct initialisation).

(Incidentally, I would be interested in your opinion of my STL
pass-by-value example in my response to Jason Merrill in this thread.
Do you think 12.8/15 should be allowed to be applied in that case?).

Having said that, I still think that the NRVO and RVO do alter the
semantics of return-by-value and I don't agree with your assessment
that the  MV_Vector_double class or my example class is broken. If one
wants to use a vector class with C arrays then such a design is
required. Some classes do share resources in ways inconsistent with
pure value or reference semantics and in particular when garbage
collection is available as memory is then viewed as owned by the
system and not by any particular object.

My philosophy is that the standard's semantics should be well-defined
and consistent and hence to allow semantics-changing optimisations
only under user control.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/17
Raw View
On 16 Jun 97 16:59:22 GMT, Jason Merrill <jason@cygnus.com> wrote:

>>>>>> Pete Becker <petebecker@acm.org> writes:
>
>>  Skipping the copy constructor is not legal. The object A consists of
>> the storage allocated for A itself and the array 'a'.
>
>That's an unusual definition of 'object', which does not match the
>definition in [intro.object].
>
>I thought we discussed this before and decided that 12.8 was referring to
>the actual object itself, not the object and everything it points to.  So
>the example would produce surprising results in the presence of the RVO.
>
>But I'm OK with that.  I'm comfortable saying that playing games with
>references and values in the same class, and relying on the copy
>constructor to switch between them, is asking for trouble.
>
>Jason

Trouble is, the STL also plays aliasing games. In fact, many classes
using dynamically allocated memory would fall prey to the 12.8/5 bugs.
 I repeat here a previously posted example using STL (it involves
12.8/5 being used to elide the copy on pass-by-value).

Note that the difference between the bugs introduced by eliding copies
on pass-by-value and on return-by-value (i.e. the RVO) is mainly one
of degree- they would be exceedingly common in the pass-by-value case
and uncommon in the return-by-value case (but just because they would
be uncommon is no reason to ignore them).

---8< -------------------------------------------------------------
#include <vector>
#include <algorithm>
#include <iostream>
#include <functional>
using namespace std;

vector<int> reverse_vec(vector<int> v)
{
 // v passed by-value, so we can safely modify it in-place
 reverse(v.begin(), v.end());
 return v;
}

int main()
{
 vector<int> v(4);
 v[0] = 1; v[1] = 2; v[2] = 3; v[3] = 4;

 vector<int>::iterator it1 = v.begin();
 vector<int>::iterator it2 = v.end();
 // vector v is never (directly) used again, and it1 and it2
 // point to internal
 // dynamically-allocated memory i.e. not to any part of the
 // object v,
 // so by 12.8 [class.copy] paragraph 15 the call to
 // reverse_vec can be silently
 // changed by the compiler to pass by-reference

 vector<int> v2 = reverse_vec(v);

 transform(it1, it2, v2.begin(), ostream_iterator<int>(cout,
","), plus<int>());

 // would expect output to be "5,5,5,5,"
 // but [class.copy] paragraph 15 allows "8,6,4,2,"
 // to be output!

 return 0;
}

-- end example--------------------------------------------------

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: fjh@mundook.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/17
Raw View
Pete Becker <petebecker@acm.org> writes:

 >Fergus Henderson wrote:
 > >
 > > Could you give us an example where a compiler could take advantage
 > > of this rule (12.8[class.copy]/15), assuming your interpretation,
 > > but where the compiler couldn't already perform the same optimization
 > > by the "as-if" rule?
 >
 >#include <iostream>
 >
 >class C {
 >public:
 > static int destructor_count;
 > ~C() { destructor_count++; }
 >};
 >
 >int C::destructor_count = 0;
 >
 >void f( C c ) { }
 >
 >int main() {
 > C c;
 > f(c);
 > if( C::destructor_count != 0 )
 >  cout << "Already got a destructor call\n";
 > return 0;
 >}

But in this example, `C::destructor_count' is conceptually part of all
C objects.  Thus the reference to `C::destructor_count != 0' would
count as a use of both the `c' in main() and the `c' in f().  Hence
12.8/15 doesn't apply, because the objects in question are not unused.

OK, so where did I go wrong?

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Mattias Lundstroem <ehsmalu@aom.ericsson.se>
Date: 1997/06/17
Raw View
Jason Merrill wrote:
> I thought we discussed this before and decided that 12.8 was referring to
> the actual object itself, not the object and everything it points to.  So
> the example would produce surprising results in the presence of the RVO.
>
> But I'm OK with that.  I'm comfortable saying that playing games with
> references and values in the same class, and relying on the copy
> constructor to switch between them, is asking for trouble.
>
> Jason

Have I missed something important in the discussions of the NRVO?

How about you have one class that refers to the internals of
another class. This is a rather common pattern with perhaps
the most clear example being containers and iterators.

My understanding is that the rule could break code like
that supplied by Brian Parker(below). Did anyone refute this?

--8<------------------------------------------------------------------------------------
// example of faulty paragraph 15
#include <vector>
#include <algorithm>
#include <iostream>
#include <functional>
using namespace std;

vector<int> reverse_vec(vector<int> v)
{
        // v passed by-value, so we can safely modify it in-place
        reverse(v.begin(), v.end());
        return v;
}

int main()
{
        vector<int> v(4);
        v[0] = 1; v[1] = 2; v[2] = 3; v[3] = 4;

        vector<int>::iterator it1 = v.begin();
        vector<int>::iterator it2 = v.end();
        // vector v is never (directly) used again, and it1 and it2 point to
internal
        // dynamically-allocated memory i.e. not to any part of the object v,
        // so by 12.8 [class.copy] paragraph 15 the call to reverse_vec can be
silently
        // changed by the compiler to pass by-reference

        vector<int> v2 = reverse_vec(v);

        transform(it1, it2, v2.begin(), ostream_iterator<int>(cout, ","),
plus<int>());

        // would expect output to be "5,5,5,5,"
        // but [class.copy] paragraph 15 allows "8,6,4,2," to be output!

        return 0;
}
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: boukanov@sentef1.fi.uib.no (Igor Boukanov)
Date: 1997/06/17
Raw View
Mattias Lundstroem (ehsmalu@aom.ericsson.se) wrote:
> My understanding is that the rule could break code like
> that supplied by Brian Parker(below). Did anyone refute this?

> // example of faulty paragraph 15
> #include <vector>
> #include <algorithm>
> #include <iostream>
> #include <functional>
> using namespace std;

> vector<int> reverse_vec(vector<int> v)
> {
>         // v passed by-value, so we can safely modify it in-place
>         reverse(v.begin(), v.end());
>         return v;
> }

As I undestand the current rule the argument for reverse_vec should be copied
because it is used when "reverse(v.begin(), v.end())" is called.
The rule just permits not to copy v on the return. So the example output
should be 5,5,5,5 even with the optimisation on.

--
Regards, Igor Boukanov.
igor.boukanov@fi.uib.no
http://www.fi.uib.no/~boukanov/
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Pete Becker <petebecker@acm.org>
Date: 1997/06/17
Raw View
Fergus Henderson wrote:
>
> Pete Becker <petebecker@acm.org> writes:
>
>  >Fergus Henderson wrote:
>  > >
>  > > Could you give us an example where a compiler could take advantage
>  > > of this rule (12.8[class.copy]/15), assuming your interpretation,
>  > > but where the compiler couldn't already perform the same optimization
>  > > by the "as-if" rule?
>  >
>  >#include <iostream>
>  >
>  >class C {
>  >public:
>  >      static int destructor_count;
>  >      ~C() { destructor_count++; }
>  >};
>  >
>  >int C::destructor_count = 0;
>  >
>  >void f( C c ) { }
>  >
>  >int main() {
>  >      C c;
>  >      f(c);
>  >      if( C::destructor_count != 0 )
>  >              cout << "Already got a destructor call\n";
>  >      return 0;
>  >}
>
> But in this example, `C::destructor_count' is conceptually part of all
> C objects.  Thus the reference to `C::destructor_count != 0' would
> count as a use of both the `c' in main() and the `c' in f().  Hence
> 12.8/15 doesn't apply, because the objects in question are not unused.
>
> OK, so where did I go wrong?
>

 You know, after I posted this I realized it was too clever. Making
destructor_count a global variable makes it clearer. I'm pretty sure
that I think static data is not part of an object, but that's an
unnecessary complication here.
 The real point of all this is this:

class C1
{
public:
 C() {}
 void set_data( int i ) { data[0] = i; }
 int get_data() const { return data[0]; }
private:
 int data[1];
};

class C2
{
public:
 C() : data(new int) {}
 // suitable assignment operator and copy constructor, of course...
 ~C2() { delete data; }

 void set_data( int i ) { data[0] = i; }
 int get_data() const { return data[0]; }
private:
 int *data;
};

 I don't see any fundamental difference between these two classes,
merely a difference in implementation. A definition of "object" that
says they have different properties is wrong. Internally they are
somewhat different, but from the outside they present exactly the same
interface and exactly the same states and behaviors.
 -- Pete
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/17
Raw View
On 17 Jun 97 09:07:07 GMT, jpotter@falcon.lhup.edu (John Potter)
wrote:
>On 16 Jun 97 13:45:24 GMT, bparker@gil.com.au (Brian Parker) wrote:
>
>: Allowing return-by-value not to make a true copy via the copy
>: constructor is semantically equivalent to returning a bit-wise copy,
>: and so if for-
>:...
>: the implementation is allowed to store a bitwise copy of A in B (or,
>: equivalently, to construct A directly in B)
>
>Effectively, it removes the named variable A and uses the return value
>area which happens to be B due to an optimization in the caller code.
>Since A does not exist, neither of the above statements is strictly
>correct.
>
>: then by that same
>: argument, for-
>
>: vector<int> A(3);  // global
>: vector<int> Copy_array()
>: {
>:  return A;
>: }
>: vector<int> B = Copy_array();
>
>: the implementation is again allowed to save a bitwise copy of A in B,
>: and hence the global A and B share the dynamically-allocated memory of
>: A between them, and hence vector<int> cannot safely be treated as a
>: simple value type, as is intended.
>
>This is just plain wrong.  A, B and Copy_array have external linkage
>and there is no way that the implementation can show that either A or
>B is not used again or that this is the only use of Copy_array.  It
>certainly has nothing to do with (N)RVO.
>..

Yes, this example was not about the NRVO per se- I was trying to make
the point that (ignoring semantics-preserving optimisations), in
general return-by-value does entail returning a true *copy* of a value
and not just an exact bit-wise image of the return value.
(Actually, I fear I may have misinterpreted  the argument in the
original post that I was replying to here- my apologies if this is the
case)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: fjh@murlibobo.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/17
Raw View
bparker@gil.com.au (Brian Parker) writes:

>vector<int> reverse_vec(vector<int> v)
>{
> // v passed by-value, so we can safely modify it in-place
> reverse(v.begin(), v.end());
> return v;
>}

Note that this example still fails even if you write it like this:

 vector<int> reverse_vec(vector<int> v) {
  vector<int> copy = v;
  reverse(copy.begin(), copy.end());
  return copy;
 }

According to 12.8/15, a compiler could still optimize away all the copies.

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: fjh@murlibobo.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/06/17
Raw View
Pete Becker <petebecker@acm.org> writes:

>Fergus Henderson wrote:
> >
> > I see three alternatives:
> >
> >         1a.  Leave the rule in there, but clarify that "object"
> >              means "Humpty-Dumpty object" for the purposes of this
> >              rule.
>
>Sigh. More ad hominem argument.

My copy of the Concise Oxford Dictionary defines "ad hominem"
as "To the man, personal".  My language may be colorful,
but it is directed squarely at the issues, not at the people
involved.  I have considerable respect for you and the other
people who have been involved in this debate.

However, I am at a loss to know exactly what you think we should
define "object" as for the purposes of this rule.  If you would
care to elaborate, I'd be interested in what you have to say.

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/06/17
Raw View
On 17 Jun 97 09:10:06 GMT, bparker@gil.com.au (Brian Parker) wrote:

: On 16 Jun 97 17:46:06 GMT, jpotter@falcon.lhup.edu (John Potter)
: wrote:

: Your argument is essentially that in the same way that
: Vec v3 = Vec(c_array);    // (1)
: can be transformed by the compiler to
: Vec v3(c_array);    // (2)
: then the return value of InitVector can be optimised away.

Yes

: I would agree with this argument, except that I don't think that even
: this optimisation is allowed by 12.2, and so it too relies upon
: 12.8/15.

I don't think so.  12.2/1 lists places where the implementation may
need to create temporaries.  It requires that the form using the
temporary must be well formed under the semantics restrictions of the
language, such as an accessable copy constructor.  Given that, the
implementation may avoid creating the temporary.  Following the links
to the places where creation of the temporary is described, there is
always a link back to 12.2 stating that the temporary may be avoided.
As I see it, 12.2 covers removal of unnamed temporaries while 12.8/15
extends this to named copies.  Both clauses are authorized violations
of the "as if" rule because they can be detected via side effects in
the ctors/dtor.

: So, if one interprets "object" in 12.8/15 to mean memory used both
: directly and indirectly, a compiler *cannot* convert (1) to (2) in
: cases such as my example- in any case, it is relying upon 12.8/15.

I do not even want to think about the ramifications of that
interpretation being inconsistently used in a few places in chapter 12
much less consistently throughout the draft.

: >  Restricting the use of 12.8 for
: >return value optimization would provide a way to code functions which
: >allow broken classes to operate, at the expense of preventing
: >temporary removal in functions using correct classes.  Use of the
: >broken classes can be coded as construction followed by assignment
: >without changing the draft.

: (actually, the way 12.8/15 is written it can elide *any* copy
: including those done via a copy assignment operator, but that is just
: an editorial problem)

Hum.  Seems I was corrected on that previously, possibly in some email
not publicly.  That may well be more than an editorial problem.

: Let me make it clear that I don't want 12.8/15 restricted *just* to
: prevent the return value optimisation. In fact my third preferred
: option would be to restrict 12.8/15 to apply *only* to initialisations
: and, therefore, the RVO. As I have previously argued, I think that
: allowing 12.8/5 to elide copies on pass-by-value is the main flaw and
: I think most people have underestimated the danger in destroying the
: fundamental axiom that pass-by-value can't alter its argument. The
: NRVO and RVO only occur in a much more circumscribed situation- i.e.
: initialisations- and so the problems can be worked around provided one
: is aware of them (by, as you suggest, using default construction
: followed by assignment rather than direct initialisation).

Vec v;
v = InitVec(c_array);
Optimize to
Vec v(InitVec(c_array));

Does 12.8/15 allow that?  Does it generally allow the implementation
to assume that construction followed by assignment can be reduced to
copy construction?

: (Incidentally, I would be interested in your opinion of my STL
: pass-by-value example in my response to Jason Merrill in this thread.
: Do you think 12.8/15 should be allowed to be applied in that case?).

Extracting the important parts
vector<int> reverse_vec(vector<int> v) {
 reverse(v.begin(), v.end());
 return v;
 }
vector<int> v1; // assign values and record begin() and end()
vector<int> v2 = reverse_vec(v1);  // v1 not used after this

Translation of last line
vector<int> v2((vector<int> v(v1), reverse(v.begin(), v.end()), v));
Optimize out v since v1 is not used again
vector<int> v2((reverse(v1.begin(), v1.end()), v1));
Optimize out v2 and replace all further uses with v1
reverse(v1.begin(), v1.end());

Seems like it is allowed.  Scary?  Do I think that it should be
allowed?  No strong feelings; so, I am willing to allow it to get
(N)RVO.  Does it scare me?  Not any more than any use of iterators.
Iterators are abstractions of pointers and just as dangerous.  In STL,
they export the internals of the container class which is always
dangerous.  I get scared any time that I declare a variable of type
iterator.  I try to only pass begin()/end() to functions.  Sometimes I
use an iterator in a for statement, and become very careful of its use
in the body.  Any more global use is cause for redesign consideration
or extreme care.

And, of course, your example is fixed by using my cautions
 transform(v1.begin(), v1.end(), v2.begin(),
   ostream_iterator<int>(cout,","), plus<int>());
The real question is whether the example was written by Machiavelli or
Murphy?

: Having said that, I still think that the NRVO and RVO do alter the
: semantics of return-by-value and I don't agree with your assessment
: that the  MV_Vector_double class or my example class is broken. If one
: wants to use a vector class with C arrays then such a design is
: required.

Well, maybe not broken, but dangerous.  I think that the idea in the
MV class is to allow a vector on the stack by exporting implementation
details to the user.  This may be desirable; however, it sure makes it
easy to produce incorrect code.

Vec v(new int[5]);  // memory leak
I am using lots of Vecs of size three and would like to be able to
initialize them nicely.  How about a useful function:
Vec BuildThreeVec (int v0, int v1, int v2) {
 int temp[3] = { v0, v1, v2 };
 return temp;
 }
Vec v1(BuildThreeVec(1, 2, 3));
Vec v2(BuildThreeVec(4, 5, 6));
cout << v1[0] << ' ' << v1[1] << ' ' << v1[2] << endl;

I think that I am, and you have been, playing Machiavelli.  Maybe
Murphy is capable of this.

John
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/17
Raw View
On 17 Jun 1997 09:25:18 PDT, boukanov@sentef1.fi.uib.no (Igor
Boukanov) wrote:
>...
>> vector<int> reverse_vec(vector<int> v)
>> {
>>         // v passed by-value, so we can safely modify it in-place
>>         reverse(v.begin(), v.end());
>>         return v;
>> }
>
>As I undestand the current rule the argument for reverse_vec should be copied
>because it is used when "reverse(v.begin(), v.end())" is called.
>The rule just permits not to copy v on the return. So the example output
>should be 5,5,5,5 even with the optimisation on.

No, in fact the draft gives exactly this case as an example in 12.8/15

"
void g(Thing t) {t.func( ); }

int main()
{
 Thing t2;
 ...

 g(t2);
}

Although g uses its parameter, the call to g(t2) does not need to copy
t2 because t2 is not used again after it is passed to g."

(as always, this depends upon your definition of "object is used".)

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Pete Becker <petebecker@acm.org>
Date: 1997/06/18
Raw View
Fergus Henderson wrote:
>
> Pete Becker <petebecker@acm.org> writes:
>
> >Fergus Henderson wrote:
> > >
> > > I see three alternatives:
> > >
> > >         1a.  Leave the rule in there, but clarify that "object"
> > >              means "Humpty-Dumpty object" for the purposes of this
> > >              rule.
> >
> >Sigh. More ad hominem argument.
>
> My copy of the Concise Oxford Dictionary defines "ad hominem"
> as "To the man, personal".  My language may be colorful,
> but it is directed squarely at the issues, not at the people
> involved.  I have considerable respect for you and the other
> people who have been involved in this debate.

My Oxford Encyclopedic Dictionary also says "(of an argument) appealing
to the emotions and not to reason", which is how I was using it. You
need to upgrade your dictionary. <g>
 -- Pete
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]