Which standard wording tells us that ref-to-const temporary lifetime extension only "works once"?

I was shown the following example in chat:

#include <iostream>
struct foo { ~foo() { std::cout << "destroying!\n"; } };
const foo& func(const foo& a, const foo&) { return a; }

int main()
{
  foo x;
  const foo& y = func(foo(), x);
  std::cout << "main\n";
}

Output:

destroying!
main
destroying!

It appears to demonstrate that the lifetime of the foo temporary is not extended to entirety of main, even though it gets bound to a ref-to-const in that scope.

Presumably, then, the lifetime extension only "works once"; that is, it is applied when func's arguments are initialised, but isn't passed on through consecutive bindings.

Is my interpretation correct? If so (and if any individual paragraph is directly applicable) what's the standard wording that defines this behaviour?


Solution 1:

You're almost right. This behaviour actually comes from the function call specifically, not because of any sort of "only works once" rule.

Here's the wording for the whole lifetime extension "feature", with the pertinent rule emphasised in bold:

[C++11: 12.2/5]: [..] 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:

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

Solution 2:

This is subject of two issue reports, http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1299 and http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1568 .

The former issue report, of which I am the reporter, was intended to cover all these cases where a reference is bound to a temporary object, but is not intended to be lifetime-extending. The description in the body of the issue only mentions prvalues being confused with temporary expressions (that actually decide whether lifetime of what they evaluate to is lengthened or not). But lvalue and xvalues are likewise confused with these in the Standard. An example where that happens in the context of static_cast is issue number #1568 (in which the use of "temporary variable" further confuses the matter).

Actually, this:

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.

Contradicts the other rules in the same paragraph. Because the temporary is bound to both a reference parameter in a function call and to a local automatic reference variable.

Solution 3:

The rule which applies here is common sense. The standard is poorly worded, and does in fact guarantee this. But there's no practical way to implement it.

Solution 4:

Probably I am a bit slow but to me it did not become clear what the resolution of this question is from reading the other answers. Thus I modified the code shown and wanted to summarize for others: the answer is, you get undefined behavior if you access y!

Run this code:

struct foo {
    int id;
    foo(int id) : id(id) { std::cout << "ctor " << id << std::endl; };
    ~foo() { std::cout << "dtor " << id << std::endl; }
};
const foo& func(const foo& a, const foo&) { return a; }

int main(int argc, char** argv) {
    foo x(1);
    const foo& y = func(foo(2), x);
    std::cout << "main " << y.id << std::endl;
    return 0;
}

The output for me is:

ctor 1
ctor 2
dtor 2
main 2
dtor 1

But the line main 2 is undefined behavior.