From: jimad@microsoft.com (Jim Adcock)
Subject: Re: return values and destructors
Date: 12 Nov 92 22:47:56 GMT
Organization: Microsoft Corporation

In article <BxKs87.417@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
|
|In section 6.6.3 on page 90 the ARM states:
|
|"A return statement with an expression can be used only in functions
|returning a value; the value of the expression is returned to the
|caller of the function."

First note, that like ANSI-C, C++ does not seem to require return-by-copy,
allowing instead return-by value.  This in no way, however, excuses
compiler from making a copy of the returned value of the expression if
such a copy is required in order to maintain the correct value of that
return expression as evaluated in sequence and in scope.

|Given the statement
|
|	return foo;
|
|where foo is an object with some value, does the statement from the
|ARM mean that the compiler *must* return the value that foo has in the
|return statement, rather than the value foo might have at some point
|after the destructors for local automatic objects have been executed?

Consider the not-uncommon case where foo is an auto-variable of the 
function returning foo and foo is of a class with a dtor.  This common
case demonstrates that the scope that the return value expression must 
the same as the scope that the return expression is in.  Otherwise foo
would be dead before evaluated.  Thus evaluation of the expression 
must occur before exiting scope.  Dtoring occurs *at* exit of scope.  
Thus the evaluation of the expression occurs before dtoring.  If dtoring 
then affects an evaluation that must occur before dtoring, clearly 
this must be a compiler bug.  If the rules were otherwise you could 
not reliably return-by-value an auto-variable object of a class with a dtor.

|Consider this example:
|
|template<class T>
|class A
|{
|  public:
|    A(T *p) : ptr(p) {}
|    ~A() {if (ptr) *ptr = 0;}
|  private:
|    T *ptr;
|};
|
|
|int
|func1()
|{
|    int x = 10;
|    A<int> a(&x);
|    return x;
|}
|
|
|func1() returns 0, not 10, even though the value of
|x in the return statement is clearly 10.  The value of x is changed to
|0 due to the destructor of the A object going out of scope at the end
|of the function.

As you point out the value of x in the return statement is clearly 10.
The evaluation of the return expression then is the evaluation of x
is clearly 10.  The returned value is the evaluation of the return expression
is the evaluation of x is clearly 10.  Anything else is clearly a bug.

|What's more interesting is that if a func2() is created that returns
|by value an object of a class type with a constructor and destructor,
|the desired value is returned because of the generation of a
|temporary:
|
|class B
|{
|  public:
|    B(int x) : val(x) {}
|    ~B() {}
|    void operator=(int x) {val = x;}
|    operator int() {return val;}
|  private:
|    int val;
|};
|
|B
|func2()
|{
|    B b(10);
|    A<B> a(&b);	// same A as above
|    return b;
|}
|
|
|In this case, func2() returns a B whose val member has a value of 10,
|due to the generation of a temporary, even though the same A
|destructor sets the value to 0.
|
|Shouldn't both of these cases perform the same way?  

Yes.

|Is this a compiler bug?  

Bug.

|Shouldn't the value of the return expression be returned to the
|caller regardless of side effects of destructors for auto objects?

Yes because clearly evaluation of an expression in scope, including
the evaluation of the expression in the return statement, are evaluated
in scope, and dtor'ing doesn't happen until exiting of scope, which is later.

|Does the language enforce any of this?  

If when the spec is complete, it ought to be complete and specific enough
to make these issues unarguable.  These issues of return-by-value, 
return-by-copy, etc, have been kicking around for many years in the 
ANSI-C committee and now C++, and ought to just be nailed down once and
for all.