This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 110b. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2023-01-15


392. Use of full expression lvalue before temporary destruction

Section: 6.7.7  [class.temporary]     Status: CD1     Submitter: Stephen Clamage     Date: 21 Nov 2002

[Voted into WP at March 2004 meeting.]

class C {
public:
    C();
    ~C();
    int& get() { return p; } // reference return
private:
    int p;
};

int main ()
{
    if ( C().get() ) // OK?
}

Section 6.7.7 [class.temporary] paragraph 3 says a temp is destroyed as the last step in evaluating the full expression. But the expression C().get() has a reference type. Does 6.7.7 [class.temporary] paragraph 3 require that the dereference to get a boolean result occur before the destructor runs, making the code valid? Or does the code have undefined behavior?

Bill Gibbons: It has undefined behavior, though clearly this wasn't intended. The lvalue-to-rvalue conversion that occurs in the "if" statement is not currently part of the full-expression.

From section 6.7.7 [class.temporary] paragraph 3:

Temporary objects are destroyed as the last step in evaluating the full-expression (6.9.1 [intro.execution]) that (lexically) contains the point where they were created.

From section 6.9.1 [intro.execution] paragraph 12:

A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.

The note in section 6.9.1 [intro.execution] paragraph 12 goes on to explain that this covers expressions used as initializers, but it does not discuss lvalues within temporaries.

It is a small point but it is probably worth correcting 6.9.1 [intro.execution] paragraph 12. Instead of the "implicit call of a function" wording, it might be better to just say that a full-expression includes any implicit use of the expression value in the enclosing language construct, and include a note giving implicit calls and lvalue-to-rvalue conversions as examples.

Offhand the places where this matters include: initialization (including member initializers), selection statements, iteration statements, return, throw

Proposed resolution (April 2003):

Change 6.9.1 [intro.execution] paragraph 12-13 to read:

A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition. Conversions applied to the result of an expression in order to satisfy the requirements of the language construct in which the expression appears are also considered to be part of the full-expression.

[Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (7.6.20 [expr.comma]). For example, in 9.4 [dcl.init] one syntax for initializer is

but the resulting construct is a function call upon a constructor function with expression-list as an argument list; such a function call is a full-expression. For example, in 9.4 [dcl.init], another syntax for initializer is but again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ] [Example:

  struct S {
      S(int i): I(i) { }
      int& v() { return I; }
    private:
      int I;
  };

  S s1(1);           // full-expression is call of S::S(int)
  S s2 = 2;          // full-expression is call of S::S(int)

  void f() {
      if (S(3).v())  // full-expression includes lvalue-to-rvalue and
                     // int to bool conversions, performed before
                     // temporary is deleted at end of full-expression
      { }
  }

end example]