17

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?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212

3 Answers3

8

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

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • +1 (when I get more votes in 16 minutes) for pointing out that `cout` may not even exist yet. – Lightness Races in Orbit Apr 22 '11 at 23:43
  • @Jerry if I do `bool b = true; b = false;` does that mean i'm doing undefined behavior? Because I don't see the writes to `b` being ordered. In C++03 it seems they could happen in between the same sequence points, and in C++0x they seem to be two unordered side effects. – Johannes Schaub - litb Apr 22 '11 at 23:49
  • @litb: Two statements with a sequence point between them, no? – Lightness Races in Orbit Apr 23 '11 at 00:18
  • Maybe litb meant `bool b = true, b = false;`? – ildjarn Apr 23 '11 at 00:19
  • @tomalak no I actually meant it. The spec doesn't say anywhere that there is a sequence point between two statements, as far as I can see. Nor does the C++0x spec say anywhere that two statements are sequenced under the sequenced-before relation. EDIT: But the C++0x spec says "statements are executed in sequence". That would be a very sloppy way to say "sequenced before the lexically next statements" or such. And since C++03 said the exact same, it definitely isn't an intended means to make the above defined behavior, I think. – Johannes Schaub - litb Apr 23 '11 at 00:26
  • @litb: Wouldn't that make `a++; std::cout << a;` UB? Am I misunderstanding what a `statement` is? – Lightness Races in Orbit Apr 23 '11 at 00:28
  • @Tomalak, since `a++` and `std::cout << a` are both full-expressions, they would both be separated by sequence points (C++03) or both be set in relation under "sequenced-before" (C++0x). – Johannes Schaub - litb Apr 23 '11 at 00:29
  • 2
    @litb: If `bool b = true; b = false;` is UB, then to my mind that's a serious flaw in the standard. – Lightness Races in Orbit Apr 23 '11 at 00:31
  • Re cout: C++0x guarantees that if you include ``, then `cout` is constructed before any use of it in that TU IIRC after the `#include` . I was assuming such guarantee in my question :) – Johannes Schaub - litb Apr 23 '11 at 00:44
  • 1
    @Johannes: it says (C++03, §1.9/16): "There is a sequence point at the completion of evaluation of each full-expression." Coupled with statements being executed in order, that seems to give your `bool b=true; b=false;` example defined behavior. N3242 §1.9/14 uses different phrasing, but gives essentially the same guarantee. The problem here is that we have two separate things that need to happen (destruction of A() and initialization of b) but no ordering compared to each other. – Jerry Coffin Apr 23 '11 at 00:54
  • As far as cout goes, I was going by C++03. C++0x tightens the requirement somewhat, to give this defined behavior. – Jerry Coffin Apr 23 '11 at 00:55
  • @Jerry the full expression in `bool b = true` is `true`. The write to `b` doesn't appear as part of any expression. So why does "There is a sequence point at the completion of evaluation of each full-expression." apply? -.- I think i'm confused by all the indirection in the spec. – Johannes Schaub - litb Apr 23 '11 at 00:56
  • @Johannes: Sorry, you're right. I think it's still defined though. This is static initialization, which must be done before all dynamic initialization (§3.6.2/1). Then dynamic initialization must either precede execution of anything in `main`, or any use of the function or object in the TU (§3.6.2/3). I'd have to check to be sure, but I believe assigning to qualifies as "use", which would mean the static initialization must be done before the assignment can be. Since the `b=false;` is an assignment, the static initialization must be done before it can execute. The wording is sloppy though. – Jerry Coffin Apr 23 '11 at 01:03
  • @Jerry I mean if you have a local, like `int main() { bool b = true; b = false; }`. Different code than in my answer, without any globals or the like. – Johannes Schaub - litb Apr 23 '11 at 01:05
  • @Johannes: In that case, yes, I think about all you get is that the declaration statement and the assignment statement are executed in order. N3242 §6.2/1 does say give *slightly* more precise wording: "All side effects from an expression statement are completed before the next statement is executed." – Jerry Coffin Apr 23 '11 at 01:12
  • @Jerry but `bool b = true` is not an expression statement -.- – Johannes Schaub - litb Apr 23 '11 at 01:30
  • @Johannes: Oops -- yes, I'm not sure how I skipped over that minor detail! You're right: that leaves only the "Except as indicated, statements are executed in sequence." Though I think the intent from that is (fairly) clear, it certainly leaves a lot to be desired in the way of precision. – Jerry Coffin Apr 23 '11 at 01:38
  • Re diagram: I'm not sure where the nodes "create temporary copy of true" and "destroy temp bool" come from (I don't think there is any `bool` temporary in my code), but in everything else of the diagram, I agree with you. – Johannes Schaub - litb Apr 23 '11 at 02:09
  • Except of course I would draw the "initialize b with result from yield()" in parallel with "End of full expression", as I explained in a comment to @Tomalak's answer. – Johannes Schaub - litb Apr 23 '11 at 02:30
  • @Johannes: After more rereading I've redrawn the diagram (yet again) and added a quote that (I think) supports how it's currently drawn. – Jerry Coffin Apr 23 '11 at 03:21
  • 12.2p4 only applies to temporaries though. In our case, there is no temporary. So "In that context, the temporary that holds the result of the expression shall persist ..." cannot be meaningfully applied. It can only be applied to class type expressions, since those, when returned from functions, are temporaries (12.2p1). It fundamentally makes no sense to talk about "temporary" for things like `bool()` or `bool f() { return true; }`, because what is temporary? The value `true` is immaterial. And there is no object. The "temporary" in the spec refers to the limited lifetime of objects. – Johannes Schaub - litb Apr 23 '11 at 03:24
  • It's not *entirely* clear to me whether 12.2/4 applies or not. The previous paragraphs specifically refer to temporary objects *of class type*, whereas 12.2/4 just talks about temporary objects. They may have taken for granted that by then you realize it's only class objects, or they may have meant that this paragraph can apply whether the object is of class type or not. In N3242, that whole section has been removed. – Jerry Coffin Apr 23 '11 at 03:37
  • @Jerry the whole text of 12.2/4 is gone in C++0x, so any discussion about its sanity is only of limited use :) But really, there can of course be temporaries of non-class types, but those are created by the implementation itself (when it needs the address of something, e.g if you do `bool const& b = true;` it creates a bool temporary and binds `b` to it). There is no harm done by saying "temporary" in general at 12.2/4. If they later add other kind of temporaries, they wouldn't need to change 12.2/4. But anyway, 12.2/4 in that form is gone in C++0x. – Johannes Schaub - litb Apr 23 '11 at 03:42
  • I think given the current wording, its (modulo the as-if rule) required to create a temporary bool holding the value `true`, and do initialization from a copy of that. It's probably *not* what was intended, but it is what that section seems (to me) to actually require. – Jerry Coffin Apr 23 '11 at 03:42
  • @Jerry I think nowhere it says that the prvalue return of a function that returns a non-class type is a temporary. Where do you say it requires that? – Johannes Schaub - litb Apr 23 '11 at 03:43
  • I don't -- as I said, that whole section is gone, and I don't see an analog in C++11. I've redrawn the diagram (again!) to show the difference. – Jerry Coffin Apr 23 '11 at 03:55
  • @Jerry you seem to say so, as you have different diagrams for C++11 and C++03, with C++03 saying that there would be somewhere a bool "temporary", but there isn't any difference in those specs for this. There would be a difference in the specs if `yield()` would return a reference to `bool` or a non-bool type. But as the code is written, both specs should have completely identical diagrams. – Johannes Schaub - litb Apr 23 '11 at 03:59
  • @Johannes: I disagree -- there clearly *is* a difference in the specs for this. There's no avoiding the fact that this is an initializer for a declarator that defines an object, therefore 12.2/4 in C++03 applies, even though it may not have been intended to. Since "prvalue" isn't defined at all in C++03, it clearly can't/doesn't apply there. – Jerry Coffin Apr 23 '11 at 04:08
  • @Jerry, this is an initializer for a declarator that defines an object. And "the temporary that holds the result of the expression shall persist until the object’s initialization is complete". Since there is no temporary that holds the result of the initializer expression in our case, we have to do nothing at all. Are you saying that in `shared_ptr a; bool b = a;`, the `a` is regarded as a temporary, and a temporary `shared_ptr` is created and then the `bool` is initialized from a temporary? – Johannes Schaub - litb Apr 23 '11 at 04:16
  • Anyway, sleep time for me :) But I think the matter is clear. – Johannes Schaub - litb Apr 23 '11 at 04:18
  • I'm not saying anything one way or the other about shared_ptr. But 12.2/4 doesn't make the temporary optional -- it doesn't say "if there is a temporary...", it says "the temporary that...", which doesn't seem (to me) to leave any room for deciding to not create a temporary. – Jerry Coffin Apr 23 '11 at 04:22
  • +1. Sometimes I say, "What a tangled web we weave," but I don't mean it literally! – Potatoswatter Apr 23 '11 at 04:38
2

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

Community
  • 1
  • 1
ildjarn
  • 62,044
  • 9
  • 127
  • 211
2

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]

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    "*I realise that b is zero-initialised to false before the specified initialisation takes place. Regardless, isn't using it in its own initialiser still UB?*" : No, because `b` has static storage duration, referring to itself during *dynamic* initialization is fine, since it is guaranteed to have been zero-initialized already. If `b` were defined/initialized so in local scope (e.g. inside `main`), or if it were initialized with a constant expression referring to itself, it would be UB. – ildjarn Apr 22 '11 at 23:56
  • ahh that makes a lot of sense. But I think that it doesn't say anywhere that the initialization of `b` has to happen before or after the full expression sequence point. What if the implementation initializes `b` directly after `yield()` returns, and before destroying the temporary `A`? The temporary would still be destroyed " as the last step in evaluating the full-expression". – Johannes Schaub - litb Apr 22 '11 at 23:57
  • @ildjarn: I know that it'll already have a value from zero-initialization, but I can't seem to find any clause regarding this UB that says it makes a distinction for dynamic initialization. I'm probably just missing it though. Still looking. – Lightness Races in Orbit Apr 22 '11 at 23:59
  • @Tomalak Geret'kal : To me it's implied by the wording of §3.6.2/1 – ildjarn Apr 23 '11 at 00:00
  • @ildjarn: I disagree, though I do suppose that, because in all cases discussing this scenario the terminology is like "using an uninitialised object", we can take "uninitialised" to mean "statically-uninitialised" if the object is static. – Lightness Races in Orbit Apr 23 '11 at 00:06
  • @Tomalak Geret'kal : But that's the point -- the *dynamic* initialization of `b` does not use any uninitialized object, as `b` is guaranteed to be zero-initialized by the time the dynamic initialization takes place. I.e., the ordering here is guaranteed, and `b` will not be uninitialized, but rather zero-initialized. – ildjarn Apr 23 '11 at 00:17