20

Consider the following code:

struct Temp{ int i = 0; };

Temp GetTemp() { return Temp{}; }

int main()
{
    for (struct{Temp const & t; int counter;} v = {GetTemp(), 0};
        v.counter < 10; 
        ++v.counter) 
    {
      // Is v.t guaranteed to not be a dangling reference?
      std::cout << v.t.i << std::endl;
    }
}

So GetTemp() returns a temporary object, which then gets assigned to a constant reference variable. However, that constant reference variable is a member of an anonymous local struct. Question: Does the C++ standard guarantee that the lifetime of that temporary gets extended till after the loop terminates?

Considering this question, I would have expected the answer to be no, i.e. that I get a dangling reference in the loop body. However, gcc and clang seem to extend the lifetime (see example on godbolt), and even do not complain with -fsanitize=undefined, which surprised me.

Mat
  • 202,337
  • 40
  • 393
  • 406
Sedenion
  • 5,421
  • 2
  • 14
  • 42
  • 1
    Why did you choose to go with an _anonymous_ structure here? I'm genuinely interested as I think this question would be equally valid with a normally declared structure. and the anonymous structure just adds complexity. – Mike Vine Jan 20 '23 at 13:31
  • Is there a specific reason you tagged this C++14? The question would equally make sense with any standard. – Columbo Jan 20 '23 at 14:59
  • 1
    The background for this question is a bug that I had to fix in a external library with horrible code. It defines `foreach(x,c)` macros, which expand to for loops. `x` is the loop variable, `c` the container. The macro definition uses `c` directly multiple times. The library sometimes calls it like `foreach(x,Get temp())`, meaning the `GetTemp` is called multiple times. I looked for ways to store the temporary properly in a local variable. But I can't just do `auto&& t=c;` in the macro definition before the for loop, as this would break in e.g. `if(...) foreach(...){}` due to missing if-braces. – Sedenion Jan 20 '23 at 17:11
  • I tagged the question c++14 since the external library where the question arose is compiled with c++14, and I wasn't sure whether something relevant changed in the various c++ versions due to the use of the braced initialization. – Sedenion Jan 20 '23 at 17:13
  • Duplicate Question. – Nadeem Taj Jan 21 '23 at 05:35

2 Answers2

12

For braced aggregate initialization as in your example, lifetime extension has been guaranteed since C++98 (irrespective of the linkage/visibility properties of the class). This is intuitive, since the reference is directly bound to the temporary, and not via some intermediate ctor parameter, as in the question you've linked. For legalese, see [class.temporary] in the C++14 FD that outlines the lifetime extension contexts.

See also the note here which differentiates braced and parenthetical initialization since C++20:

[Note 7: By contrast with direct-list-initialization, narrowing conversions ([dcl.init.list]) are permitted, designators are not permitted, a temporary object bound to a reference does not have its lifetime extended ([class.temporary]), and there is no brace elision.
[Example 3:

struct A {
    int a;
    int&& r;
};

int f();
int n = 10;

A a1{1, f()};                   // OK, lifetime is extended 
A a2(1, f());                   // well-formed, but dangling reference
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/251290/discussion-on-answer-by-columbo-does-a-constant-reference-member-variable-in-an). – Machavity Jan 20 '23 at 15:26
5

Does a constant reference member variable in an anonymous struct extend the lifetime of a temporary?

Yes, the chain of const references is unbroken. There's is only v and v is alive until the end of the for loop and the lifetime of the referenced Temp is therefore extended until then. The fact that the struct is anonymous has no impact.

class.temporary/4

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 a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.

class.temporary/5

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:

  • (5.1) A temporary bound to a reference member in a constructor's ctor-initializer ([class.base.init]) persists until the constructor exits.
  • (5.2) A temporary bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.
  • (5.3) The lifetime of a temporary bound to the returned value in a function return statement ([stmt.return]) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
  • (5.4) A temporary bound to a reference in a new-initializer ([expr.new]) persists until the completion of the full-expression containing the new-initializer.

None of the exceptions to the lifetime extension applies to your case.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 1
    Can you clarify what "Yes, the chain of const references is unbroken." means? The question the author cites also has a chain of const& if you so will, but that behaves differently. – Columbo Jan 20 '23 at 14:19
  • @Columbo I mean that one could do it in steps: `const& a = tmp; const& b = a; const& c = b; ...` - and that would be an unbroken chain. – Ted Lyngmo Jan 20 '23 at 14:31
  • You do realize that doing that would never extend the lifetime of `tmp` beyond that of `a`? – Columbo Jan 20 '23 at 14:42
  • @Columbo Jeez, I've tried to write some nice examples of broken and unbroken `const&` chains but it just becomes messy :) I'll have to give that another go Tomorrow (or I'll just remove that sentence - it doesn't appear anywhere in the standard anyway so perhaps that would be the best). – Ted Lyngmo Jan 20 '23 at 17:12