Topic: Defect Report: Lifetime of "named" temporaries?


Author: "Bill Wade" <bill.wade@stoner.com>
Date: 1999/11/17
Raw View
John Potter wrote in message <382dbfb9.23532276@news.csrlink.net>...
>On 13 Nov 99 06:04:49 GMT, "Bill Wade" <bill.wade@stoner.com> wrote:

>: >:   T bar(T x){ x.mutate(); return x; }
>: >:   T y = bar(T());    // Are any calls to T's copy constructor
required?

>: An implementation that puts all auto objects
>: into a "heap" and doesn't bind their addresses until run time seems to be
>: conforming.  On such a platform it is straightforward to apply 12.8/15 to
>: all of the T copy constructors which might otherwise be invoked, even if
>: bar is not inline.

>But let me see if I understand your statement about heap usage.  Pass
>by value is implemented using pointers.
>
> [ example deleted ]

In your example you allocated/constructed y before it was needed.

>I obviously don't understand.  Can you explain?

Suppose all auto values (including temporaries) are implemented on a heap
(meaning it is easy to release them in a non-stack order).  Call by value
and return by value are implemented by passing pointers to objects.  If we
have a function
   X foo(Y y){ y.DoSomething(); return X(); }
and a caller calls it like this
  X x;
  Y y;
  x = foo(y);
the caller's assembly code looks more like:
  X* xp = new X;
  Y* yp = new Y;
  Y* ap =new Y(*yp);
  X* rp = foo(ap);
  *xp = *rp;
  delete ap;
  delete rp;
  // delete xp,yp later when they go out of scope
and foo's assembly looks something like
  X* foo(Y* ap)
  {
    ap->DoSomething();
    return new X;
  }
If the callers code were changed to
  X x = foo(Y());
the caller can apply optimizations so that its assembly is now shortened to
  Y* ap = new Y;
  X* xp = foo(ap);
  delete ap;
[As an aside, an advantage of this scheme is that RVO can be applied for
both branches of
    X bar(){ ... return test() ? a : b; }
]

Now we go back to the original example.  The callers "assembly" is
  T* tp = new T;
  T* yp = bar(tp);
  delete tp;
  // delete yp later when y goes out of scope.
and bar is implemented as
  T* bar(T* xp)
  {
    xp->mutate();
    T* rp = new T(*xp);    // Copy required because caller deletes both xp
and rp.
    return rp;
  }
However, I believe an implementor could change the calling convention for
the case where an argument and a result had the same type so that the caller
writes
  T* tp = new T;
  T* yp = bar(tp);
  if(tp != yp)
    delete tp;
and bar would be implemented as
  xp->mutate();
  return xp;
and T's copy constructor would never get called.
---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "Bill Wade" <bill.wade@stoner.com>
Date: 1999/11/13
Raw View
John Potter wrote in message <3828d812.26955539@news.csrlink.net>...
>
>On 09 Nov 99 21:44:17 GMT, "Bill Wade" <bill.wade@stoner.com> wrote:
>
>: In section 12.8 [class.copy] paragraph 15, implementations are given too
>: much license to elide copy constructors.  The paragraph reads, in part
>: "Whenever a temporary class object is copied ... an implementation is
>: permitted to ... not perform a copy at all."
>
>:     struct A
>:     {
>:         mutable int value;
>:         explicit A(int i) : value(i) {}
>:         void mutate(int i) const { value = i; }
>:     };
>:
>:     int foo()
>:     {
>:         A const& t = A(1);
>
>At this point, we have an A const& called t.  I see no temporary.

Temporaries are objects (12.2).  References are not.  A reference may refer
to a temporary object, or it may refer to a non-temporary object.  The
standard explicitly recognizes references bound to temporaries and says
nothing to imply that such an object is no longer a temporary by virtue of
having a reference.  The lifetime of such a temporary may be modified, but
its designation as a temporary is not.

>:         A n(t);                         // 12.8/15 seems to allow &t ==
&n
>
>N is a copy of whatever t refers to.

t refers to a temporary, and the standard says that (with certain
exceptions) a copy of a temporary need not necessarily be distinct from the
temporary.  How the implementation figures out that the object is a
temporary is up to the implementation (perhaps it keeps a map of all
temporaries).

>:         t.mutate(2);
>:         return n.value;
>:     }
>:
>: it is unspecified, whether foo() will return 1 or 2, as t may be an
>: alias for n ?!
>
>I don't think so.
>
>: Any change to the definition should make it clear if the optimization is
>: allowed in the following:
>:   T bar(T x){ x.mutate(); return x; }
>:   T y = bar(T());    // Are any calls to T's copy constructor required?
>
>This one is no problem.  Inlining is not allowed to change the meaning;
>so, consider them in different translation units.
>
>The caller may send the uninitialized space for y as the space for the
>return value.  The caller may default initialize the parameter
>directly.  The callee now has uninitialized space for the return value
>and an initialized x.  It must do a copy at the return.

You're giving one implementation strategy for RVO (and probably the most
common for a stack machine).  An implementation that puts all auto objects
into a "heap" and doesn't bind their addresses until run time seems to be
conforming.  On such a platform it is straightforward to apply 12.8/15 to
all of the T copy constructors which might otherwise be invoked, even if bar
is not inline.

>Giving inlining, the as-if rule would allow more, but it must not
>change the meaning of the program.

Inlining may not perform optimizations that the standard doesn't allow for
non-inlined functions.  However inlining may perform optimizations that a
particular implementation can't do without inlining.  For instance if a
particular implementation can only share string literals (2.13.4/2) that are
in the same function, but does manage to share string literals that are
"brought in" due to inlining, the sharing is allowed, even if it changes the
meaning of the program.

>12.8/15 allows changing the
>meaning of the program, but does not apply here.  X is neither a
>temporary nor a local variable.

The actual argument which is copied into x is a temporary, so the first
sentence of 12.8/15 (also part of 5.2.2/4) seems to apply there.

For the return statement, x is a parameter.  I'm not sure if parameters are
considered locals or not.  Certainly their lifetime is defined in 3.3.2
[basic.scope.local] and their names may not be reused in the outermost block
of a function.  I agree that if parameters are not locals, then 12.8/15 does
not apply.  However if the declaration of bar is changed to
  T bar(T& x)
the question comes back up whenever x refers to a temporary object.

Clearly an implementation would have to do something unusual to make sure
that the temporary argument to bar was not destroyed before the end of y's
lifetime.
---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1999/11/13
Raw View
On 11 Nov 1999 21:02:44 GMT, "James Kuyper Jr." <kuyper@wizard.net>
wrote:

:
: John Potter wrote:
: > BTW, do you have a reference for the lifetime of a reference which is
-----------------------------------------^^^^^^^^^^^^^^^^^^^^^^^
: > not an object?  I only find lifetimes for objects.
:
: Look under the rules for lifetimes of temporaries. Section 12.2p5 covers
: the case where the temporary is bound to a reference. Note that "The
: temporary ... persists for the lifetime of the reference ..."; the
: binding doesn't change the fact that the object references is a
: temporary one.

You answered the wrong question.  I know that.  I asked what the
lifetime of the reference was.  I found it.

3.7/4 The lifetime of a reference is its storage duration.

But, 8.3.2/3 It is unspecified whether or not a reference requires
storage.

Conclusion: The lifetime of a temporary bound to a const reference
is unspecified.

Do you have a better answer?

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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1999/11/13
Raw View
On 13 Nov 99 06:04:49 GMT, "Bill Wade" <bill.wade@stoner.com> wrote:

: John Potter wrote in message <3828d812.26955539@news.csrlink.net>...
: >
: >:   T bar(T x){ x.mutate(); return x; }
: >:   T y = bar(T());    // Are any calls to T's copy constructor required?
: >
: >This one is no problem.  Inlining is not allowed to change the meaning;
: >so, consider them in different translation units.
: >
: >The caller may send the uninitialized space for y as the space for the
: >return value.  The caller may default initialize the parameter
: >directly.  The callee now has uninitialized space for the return value
: >and an initialized x.  It must do a copy at the return.
:
: You're giving one implementation strategy for RVO (and probably the most
: common for a stack machine).  An implementation that puts all auto objects
: into a "heap" and doesn't bind their addresses until run time seems to be
: conforming.  On such a platform it is straightforward to apply 12.8/15 to
: all of the T copy constructors which might otherwise be invoked, even if
: bar is not inline.

The new wording makes it clear that the parameter is available for
optimization.  It uses auto rather than local.  No doubt x is an
auto.  It has also been stated that all three copies may be elided.

In the abstract model, there is a sequence point between initialization
of the parameter and the function code.  The function must initialize
the return value and there is another sequence point between that and
the caller code.  I see no way that any view of the function code
could conclude that the parameter and the return value can be two names
for the same thing.  I see no way that any view of the function
prototype could conclude that the parameter and the return value can
be two names for the same thing.

I can't find anything other than sequence points which might contradict
your statement that it would be allowed with inlining.  I do find it
surprising that inlining would be allowed to change the observable
behavior.

This example is accademic to me since the only changes are in side
effects of the copy ctor and dtor.  The other example could be a real
problem.  I doubt that either will be a practical problem since it is
highly unlikely that any implementation will do those nasty things in
the unlikely event that anyone codes those things.  But this is csc++.

But let me see if I understand your statement about heap usage.  Pass
by value is implemented using pointers.

T* bar (T* xp) { // xp is a pointer to the parameter
   xp->mutate();
   return xp;
   }
yp = new T();
rp = bar(yp);
if (rp != yp) {
   *yp = *rp;
   delete rp;
   }

Replacing the copy construction with copy assignment is not allowed; so,
we need a convention that any function which returns a value of the
same type as a parameter will always reuse the space.  That gives

yp = new T();
bar(yp);

That works, but what about

T bar (T x) { T z(x); z.mutate(); return z; }

Z could be used for the return value, but the convention says that
we must use x.  The copy to z is required.  An assignment to x
would be required.  Can't use that convention.

I obviously don't understand.  Can you explain?

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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1999/11/11
Raw View
On 10 Nov 1999 23:45:41 GMT, AllanW <allan_w@my-deja.com> wrote:

:
: In article <3828d812.26955539@news.csrlink.net>,
:   jpotter@falcon.lhup.edu (John Potter) wrote:
: > : it is unspecified, whether foo() will return 1 or 2, as t may be an
: > : alias for n ?!
: >
: > I don't think so.
:
: I think you missed that t isn't a first-class object, but
: just a reference to a temporary.

I noticed that.  I am reading "when a temporary is copied" and noting
that a temporary is not copied, it is bound to a const reference.
The lifetime of that temporary now becomes the lifetime of that
non-object and it does not act like a temporary anymore.  The temporary
exists and is bound to the const reference.  The object n may not be
removed, thus a copy must be made.  Note that the wording carefully
removes temporaries.  Even in the return of a local variable, it is
the temporary return value that is removed, not the local variable.

I see your point also.  Happy to drop it and let the language lawyers
decide.

BTW, do you have a reference for the lifetime of a reference which is
not an object?  I only find lifetimes for objects.

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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: wmm@fastdial.net
Date: 1999/11/11
Raw View
In article <8079kh$4p0@library1.airnews.net>,
  "Bill Wade" <bill.wade@stoner.com> wrote:
>  [Moderator's note: this defect report has been forwarded to
>  the C++ committee.  -moderator (fjh).]
>
> In section 12.8 [class.copy] paragraph 15, implementations are given
too
> much license to elide copy constructors.  The paragraph reads, in part
> "Whenever a temporary class object is copied ... an implementation is
> permitted to ... not perform a copy at all."
>
> In a posting to csc++, <806je2$49o$1@news.hamburg.pop.de>, J.Barfurth
gave
> the following example, which I have edited slightly.
> >>>>>>>>>>>>>>>>>>>>>
> Following your argument, we must conclude that in the following
>
>     struct A
>     {
>         mutable int value;
>         explicit A(int i) : value(i) {}
>         void mutate(int i) const { value = i; }
>     };
>
>     int foo()
>     {
>         A const& t = A(1);
>         A n(t);                         // 12.8/15 seems to allow &t
== &n
>         t.mutate(2);
>         return n.value;
>     }
>
> it is unspecified, whether foo() will return 1 or 2, as t may be an
> alias for n ?!
>
> I doubt, that this (or the other case we discussed before, where the
> 'temporary-ness' even crosses a function call boundary) is what was
intended
> by 12.8/15. I have to admit though, that I can't find normative
wording
> against it.
> <<<<<<<<<<<<<<<<<<<<<<<<<
> His example was well defined using the version of 12.8/15 found in
CD2.  I
> believe the wording was changed with the intent of preventing some
> occurrences of this particular optimization, not to allow additional
> optimizations.
>
> The ambiguity could be resolved by changing the wording to
exclude "named
> temporaries" (or "temporaries bound to a named reference").  Such a
change
> would require
>    A n(t);
> to unconditionally perform the copy.  Alternatively wording could be
> selected which would allow the optimization in cases where the
temporary's
> name were no longer used.  With such a definition, removing the
> 't.mutate(2)' statement from the example above would allow the
optimization
> to occur.

You're correct; the intent was certainly not to apply this optimization
to temporaries that have been bound to references.

> Any change to the definition should make it clear if the optimization
is
> allowed in the following:
>   T bar(T x){ x.mutate(); return x; }
>   T y = bar(T());    // Are any calls to T's copy constructor
required?

It's too bad this wasn't reported a couple of weeks earlier; we just
accepted a DR at the meeting last month in which this paragraph was
extensively rewritten (see issue #20 in the core issues list for the
wording).  Oh, well...  I believe the revised wording we adopted is
fairly clear that an implementation is permitted to elide all three
of the copy constructions in this example.  Please let me know if you
feel further clarification is required.
--
William M. Miller, wmm@fastdial.net
OnDisplay, Inc. (www.ondisplay.com)


Sent via Deja.com http://www.deja.com/
Before you buy.
---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "James Kuyper Jr." <kuyper@wizard.net>
Date: 1999/11/11
Raw View
John Potter wrote:
....
> I noticed that.  I am reading "when a temporary is copied" and noting
> that a temporary is not copied, it is bound to a const reference.
> The lifetime of that temporary now becomes the lifetime of that
> non-object and it does not act like a temporary anymore.  The temporary

However, it still is a temporary, and the rule about copying temporaries
still applies to it, even when it's referred to through that reference.

> BTW, do you have a reference for the lifetime of a reference which is
> not an object?  I only find lifetimes for objects.

Look under the rules for lifetimes of temporaries. Section 12.2p5 covers
the case where the temporary is bound to a reference. Note that "The
temporary ... persists for the lifetime of the reference ..."; the
binding doesn't change the fact that the object references is a
temporary one.


[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Bill Wade" <bill.wade@stoner.com>
Date: 1999/11/09
Raw View
 [Moderator's note: this defect report has been forwarded to
 the C++ committee.  -moderator (fjh).]

In section 12.8 [class.copy] paragraph 15, implementations are given too
much license to elide copy constructors.  The paragraph reads, in part
"Whenever a temporary class object is copied ... an implementation is
permitted to ... not perform a copy at all."

In a posting to csc++, <806je2$49o$1@news.hamburg.pop.de>, J.Barfurth gave
the following example, which I have edited slightly.
>>>>>>>>>>>>>>>>>>>>>
Following your argument, we must conclude that in the following

    struct A
    {
        mutable int value;
        explicit A(int i) : value(i) {}
        void mutate(int i) const { value = i; }
    };

    int foo()
    {
        A const& t = A(1);
        A n(t);                         // 12.8/15 seems to allow &t == &n
        t.mutate(2);
        return n.value;
    }

it is unspecified, whether foo() will return 1 or 2, as t may be an
alias for n ?!

I doubt, that this (or the other case we discussed before, where the
'temporary-ness' even crosses a function call boundary) is what was intended
by 12.8/15. I have to admit though, that I can't find normative wording
against it.
<<<<<<<<<<<<<<<<<<<<<<<<<
His example was well defined using the version of 12.8/15 found in CD2.  I
believe the wording was changed with the intent of preventing some
occurrences of this particular optimization, not to allow additional
optimizations.

The ambiguity could be resolved by changing the wording to exclude "named
temporaries" (or "temporaries bound to a named reference").  Such a change
would require
   A n(t);
to unconditionally perform the copy.  Alternatively wording could be
selected which would allow the optimization in cases where the temporary's
name were no longer used.  With such a definition, removing the
't.mutate(2)' statement from the example above would allow the optimization
to occur.

Any change to the definition should make it clear if the optimization is
allowed in the following:
  T bar(T x){ x.mutate(); return x; }
  T y = bar(T());    // Are any calls to T's copy constructor required?
---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1999/11/10
Raw View
On 09 Nov 99 21:44:17 GMT, "Bill Wade" <bill.wade@stoner.com> wrote:

: In section 12.8 [class.copy] paragraph 15, implementations are given too
: much license to elide copy constructors.  The paragraph reads, in part
: "Whenever a temporary class object is copied ... an implementation is
: permitted to ... not perform a copy at all."

:     struct A
:     {
:         mutable int value;
:         explicit A(int i) : value(i) {}
:         void mutate(int i) const { value = i; }
:     };
:
:     int foo()
:     {
:         A const& t = A(1);

At this point, we have an A const& called t.  I see no temporary.

:         A n(t);                         // 12.8/15 seems to allow &t == &n

N is a copy of whatever t refers to.

:         t.mutate(2);
:         return n.value;
:     }
:
: it is unspecified, whether foo() will return 1 or 2, as t may be an
: alias for n ?!

I don't think so.

: Any change to the definition should make it clear if the optimization is
: allowed in the following:
:   T bar(T x){ x.mutate(); return x; }
:   T y = bar(T());    // Are any calls to T's copy constructor required?

This one is no problem.  Inlining is not allowed to change the meaning;
so, consider them in different translation units.

The caller may send the uninitialized space for y as the space for the
return value.  The caller may default initialize the parameter
directly.  The callee now has uninitialized space for the return value
and an initialized x.  It must do a copy at the return.

Giving inlining, the as-if rule would allow more, but it must not
change the meaning of the program.  12.8/15 allows changing the
meaning of the program, but does not apply here.  X is neither a
temporary nor a local variable.

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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: AllanW <allan_w@my-deja.com>
Date: 1999/11/10
Raw View
In article <3828d812.26955539@news.csrlink.net>,
  jpotter@falcon.lhup.edu (John Potter) wrote:
>
> On 09 Nov 99 21:44:17 GMT, "Bill Wade" <bill.wade@stoner.com> wrote:
>
> : In section 12.8 [class.copy] paragraph 15, implementations
> : are given too much license to elide copy constructors.  The
> : paragraph reads, in part
> :     "Whenever a temporary class object is copied ...
> :     an implementation is permitted to ...
> :     not perform a copy at all."
>
> :     struct A
> :     {
> :         mutable int value;
> :         explicit A(int i) : value(i) {}
> :         void mutate(int i) const { value = i; }
> :     };
> :
> :     int foo()
> :     {
> :         A const& t = A(1);
>
> At this point, we have an A const& called t.  I see no temporary.

At this point, t is a const REFERENCE to an A. What A does it
reference? The temporary one created with A(1).

> :         A n(t); // 12.8/15 seems to allow &t == &n

It seems that way to me!

> N is a copy of whatever t refers to.

That's the obvious intent.

> :         t.mutate(2);
> :         return n.value;
> :     }
> :
> : it is unspecified, whether foo() will return 1 or 2, as t may be an
> : alias for n ?!
>
> I don't think so.

I think you missed that t isn't a first-class object, but
just a reference to a temporary.

--
Allan_W@my-deja.com is a "Spam Magnet," never read.
Please reply in newsgroups only, sorry.


Sent via Deja.com http://www.deja.com/
Before you buy.


[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]