2

I was suprised recently to find a temporary variable in C++ being promoted to having full lexical scope:

class Foo {
public:
    Foo() {
        std::cout << "A";
    }
    ~Foo() {
        std::cout << "B";
    }
};

int main(void)
{
    // Prints "ACB", showing the temporary being promoted to having lexical scope.
    const Foo& f = Foo();
    std::cout << "C";
    return 0;
}

Aside from the dubious behavior of assigning a temporary to a reference, this actually works just fine (tested in VS2010 and G++ v4.1). The output is ACB, showing clearly that the temporary object has been promoted to having lexical scope, and is only destructed at the end of the function (B is printed after C).


Other temporary variables don't behave this way:

int main(void)
{
    // Prints "ACBD", showing that the temporary is destroyed before the next sequence point.
    const int f = ((Foo(), std::cout << "C"), 5);
    std::cout << "D";
    return 0;
}

As per my code comment this prints ACBD, showing that the temporary variable is kept until the entire expression has finished evaluating (why C is printed before B), but still destroyed before the next sequence point (why B is printed before D). (This behaviour was how I thought all temporary variables in C++ worked. I was very surprised at the earlier behaviour.)

Can someone explain when it is legal for a temporary to be promoted to have lexical scope like this?

madth3
  • 7,275
  • 12
  • 50
  • 74
pauldoo
  • 18,087
  • 20
  • 94
  • 116

1 Answers1

5

The lifetime of a temporary which is bound to a constant lvalue reference or to an rvalue reference (since C++11) is extended to the lifetime of that reference. In the second case you do not have a reference on the left side of the assignment, but a value, so the lifetime of the temporary is not prolonged.

See 12.2/4 and 12.2/5 of the C++ 11 Standard:

There are two contexts in which temporaries are destroyed at a different point than the end of the fullexpression. The first context is when a default constructor is called to initialize an element of an array [...]

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: [...]

What follows the "except" are situations that do not apply to this case.

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Interesting. It also seems like the compiler will apply some smarts here, as "f = (1, Foo())" also results in promotion. More elaborate code doesn't, such as "F = identity(Foo())". There's certainly scope for a seemingly innocuous refactor causing a real behavioural change here. Thanks for the reference though! :) – pauldoo Feb 05 '13 at 14:36
  • @pauldoo: Don't think of it as promotion. Think of it as binding. It will live as long as the `const reference` it is bound too. – Martin York Feb 05 '13 at 14:40
  • 1
    Note that what it actually says in the standard isn't what it intended to say, nor what any compiler actually implements. The actual behavior is more along the lines of "when a reference is initialized by a temporary, the lifetime of the temporary is extended to match the lifetime of _that_ reference." In other words, this extension of lifetime isn't transitive, and other references which may end up bound to the temporary (because they were initialized with the first reference) have no effect on its lifetime. – James Kanze Feb 05 '13 at 14:49
  • @JamesKanze: The wording says "[...] persists for the lifetime of *the* reference [...]". I guess it is implicit here that it's talking about the reference to which the temporary is bound – Andy Prowl Feb 05 '13 at 14:59
  • @AndyProwl The temporary can be bound to any number of references. It's lifetime is only affected by the reference where it was used in the initialization expression. – James Kanze Feb 05 '13 at 15:01
  • @JamesKanze: Yes, but I'm saying this is implied by the wording IMO – Andy Prowl Feb 05 '13 at 15:02
  • @AndyProwl I don't think so, at least not by my understanding of English. If I write something like `T const& passThrough( T const& r ) { return r; }`, and then `T const& r = passThrough( T() );`, `r` is clearly bound to a temporary (`T()`), but the lifetime of this temporary is not extended. – James Kanze Feb 05 '13 at 15:04
  • @JamesKanze: I believe the Standard rules that out explicitly. Let me try and find a reference – Andy Prowl Feb 05 '13 at 15:08
  • @JamesKanze: 12.2/5: *"The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement."* – Andy Prowl Feb 05 '13 at 15:11
  • @AndyProwl So that's how they get around it. IIRC, there's also something similar with regards to function arguments. But then, it seems contradictory: you end up with a temporary, bound to several different references, and each binding forces a different temporary. (In your last citation, too, I suspect the intent was that the fact that the return value was a reference wouldn't extend the lifetime of `return T();`.) – James Kanze Feb 05 '13 at 16:06
  • @JamesKanze: Maybe what you refer to is in the same paragraph: *"A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call."* Not sure I understand the contradiction you mention though. – Andy Prowl Feb 05 '13 at 16:39
  • @AndyProwl When you execute the code I gave, you end up with the temporary (the `T()` as argument to `passThrough`) being bound to three different references: the function parameter, the return value, and `r`. And each of the bindings is specified as having a different lifetime. – James Kanze Feb 05 '13 at 16:48
  • @JamesKanze: Yes, but only the first one extends the lifetime of the temporary. This is how I understand it at least. – Andy Prowl Feb 05 '13 at 16:51
  • @AndyProwl That's exactly what I've been saying. The wording in the standard is poor; it's not the fact that a reference is bound to the temporary which affects its lifetime, it's the fact that the initializing expression was a temporary (and not another reference). – James Kanze Feb 05 '13 at 17:04
  • @AndyProwl Why does the exception _12.2/5: "The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement."_ not apply in OPs case? – Sarien Jun 24 '14 at 09:27
  • @Sarien: There is no `return` statement in the OP's code :) – Andy Prowl Jun 24 '14 at 13:10
  • @AndyProwl Hm, okay. But assuming that he wasn't just calling Foo() but some CreateFooThingyFunction(); It seems that GotW 88 implies it should work but then it includes a return. – Sarien Jun 30 '14 at 15:48
  • @Sarien 12.2/5 refers to the situation when you do "return r" and r is a reference bound to a temporary. See the comments above. – Andy Prowl Jun 30 '14 at 18:02
  • @AndyProwl But why does it not apply in the situation where I just do something like return Foo(); it sounds like it should. I'm sorry I don't fully understand the discussion in the comments. – Sarien Jul 01 '14 at 16:20
  • @Sarien: It does apply also to the case of `return Foo()`, as far as I understand it. Mine was just an example. I'm not sure I understand what you're asking :) – Andy Prowl Jul 01 '14 at 19:51