Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When exactly is an initializer temporary destroyed?

I constructed this experiment today, after answering some question

struct A { 
  bool &b; 
  A(bool &b):b(b) { } 
  ~A() { std::cout << b; }  
  bool yield() { return true; } 
}; 

bool b = A(b).yield();

int main() { }

b has value false (resulting from zero initialization) before setting it to true by the dynamic initialization. If the temporary is destroyed before initialization of b finished, we will print false, otherwise true.

The spec says that the temporary is destroyed at the end of the full-expression. That does not seem to be ordered with the initialization of b. So I wonder

  • Does the spec allow an implementation to print both false and true in different runs?

Clang prints false for the above, while GCC prints true. This confuses me. Did I miss some spec text defining the order?

like image 470
Johannes Schaub - litb Avatar asked Apr 22 '11 23:04

Johannes Schaub - litb


3 Answers

I think it's allowed to print out true, or false, or for somewhat unrelated reasons, nothing at all.

The true or false part is (as you've said), that the destruction of the temporary A object is not ordered with respect to the dynamic initialization of b.

The nothing at all possibility is because the initialization of b is not ordered with respect to the creation/initialization of std::cout; when you try to destroy the temporary, cout may not have been created/initialized yet, so attempting to print something may not work at that point at all. [Edit: this is specific to C++98/03, and does not apply to C++11.]

Edit: here is how I, at least, see the sequence:

enter image description here

Edit2: After rereading §12.2/4 (yet again), I've changed the diagram again. §12.2/4 says:

There are two contexts in which temporaries are destroyed at a different point than the end of the full expression. The first context is when an expression appears as an initializer for a declarator defining an object. In that context, the temporary that holds the result of the expression shall persist until the object’s initialization is complete. The object is initialized from a copy of the temporary; during this copying, an implementation can call the copy constructor many times; the temporary is destroyed after it has been copied, before or when the initialization completes.

I believe this expression is an initializer for a declarator defining an object, so it's required to initialize the object from a copy of the value of the expression (true, in this case), not directly from the return value. In the case of true, this is probably a distinction without a difference, but I think the diagram is technically more accurate as it stands right now.

This also makes fairly clear (I think) that the temporary holding true does not have to be destroyed at the end of the full expression, so I've re-drawn the diagram to reflect that as well.

This section is gone in C++0x/C++11, so I've re-drawn the diagram (yet again) to show the difference between the two (and how much simpler this piece has gotten in C++11).

like image 139
Jerry Coffin Avatar answered Nov 15 '22 07:11

Jerry Coffin


(Quoting the C++03 standard)

First there's §12.2/3:

When an implementation introduces a temporary object of a class that has a non-trivial constructor (12.1), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor (12.4). Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception.

I believe this is a red herring, because of §1.9/13:

[Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (5.18). For example, in 8.5 one syntax for initializer is

    ( expression-list )

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 8.5, another syntax for initializer is

    = initializer-clause

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. ]

This implies to me that A(b).yield() is itself a full expression, rendering §12.2/3 irrelevant here.

Then we get into sequence points -- §1.9/7:

Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression might produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.

§1.9/16:

There is a sequence point at the completion of evaluation of each full-expression.

and §1.9/17:

When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function.

Putting it all together, I think Clang is right and GCC (and MSVC 2010 SP1) is wrong -- the temporary that holds the result of the expression (whose lifetime is being extended as per §12.2/4) is the bool returned from A::yield(), not the temporary A on which yield is invoked. Taking into account §1.9, there should be a sequence point after the call to A::yield() during which the temporary A is destroyed.

like image 37
ildjarn Avatar answered Nov 15 '22 06:11

ildjarn


First, just to clear up the paragraph that was previously here, using b in its own (dynamic) initialisation here is not UB. Before the expression is evaluated, b is not uninitialised but zero-initialised.


The temporary A must live for precisely as long as the full expression:

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

[ISO/IEC 14882:2003(E) 12.2/3]

The line bool b = A(b).yield(); is a declaration, which is a statement, which is not an expression. The expression at hand is found only to the RHS of the =. [ISO/IEC 14882:2003(E) A.6]

This would mean that the temporary should be destroyed before the dynamic initialisation takes place, no? Sure, the value true is held in the temporary that contains the result of the expression1 until the initialisation completes, but the original A temporary should be destroyed before b is actually modified.

Therefore I'd expect the output false, every time.


1

The first context is when an expression appears as an initializer for a declarator defining an object. In that context, the temporary that holds the result of the expression shall persist until the object’s initialization is complete"

[ISO/IEC 14882:2003(E) 12.2/4]

like image 2
Lightness Races in Orbit Avatar answered Nov 15 '22 07:11

Lightness Races in Orbit