10

Below is the code snippet:

#include <iostream>
using namespace std;
struct B{
     int b;
     ~B(){cout <<"destruct B" << endl;}
};
B func(){
    B b;
    b.b = 1;
    return b;
}
int main(){
    const B& instance = (const B&)func(); //is `instance` a dangling reference?
    cout <<instance.b<<endl;
    return 0;
}

in this online compiler the output is

destruct B
destruct B
1

So the return value seems to destruct earlier than the cout operation. So the instance seems to be a dangling reference.

If we change const B& instance = (const B&)func(); to const B& instance =func();, then the result is

destruct B
1
destruct B

As a supplement, if I test the code in vs2015, then the output is the last one. However, if tested in gcc(before 4.6) ,the output is the former one,but latter one in version after 4.6. So I want to know whether the online compiler is wrong or the reference is dangling in fact.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
choxsword
  • 3,187
  • 18
  • 44
  • You've answered your own question right in the title: your reference *isn't* bound to a temporary. – Kerrek SB May 26 '18 at 12:16
  • @KerrekSB I cannot get your point. What I mean is that `(const B&)func()` is a temporary reference bound to a temporary return object. So `instance` is bound to that temporary reference, which is a temporary. – choxsword May 26 '18 at 12:19
  • 3
    From [\[class.temporary\]/6](http://eel.is/c++draft/class.temporary#6): `const int& x = (const int&)1; // temporary for value 1 has same lifetime as x` – cpplearner May 26 '18 at 12:20
  • @cpplearner So is the online compiler making something wrong? – choxsword May 26 '18 at 12:21
  • @KerrekSB I re-edited the title to make it clearer. – choxsword May 26 '18 at 12:25
  • @KillzoneKid Do you mean that the compiler may have problem? – choxsword May 26 '18 at 12:28
  • @bigxiao: Hm, I was wrong there as far as C++17 is concerned, where the wording for lifetime extension has changed. In C++11 I believe that the expression `(const B&)func()` is not a temporary (because it's an lvalue, not a prvalue), and in C++11 only temporaries get lifetime-extended. C++17 allows glvalues to be lifetime extended (to support materialization, I suspect), and this seems to have had the side effect of allowing your cast, which now casts a glvalue to glvalue (since the prvalue has already materialized at that point). – Kerrek SB May 26 '18 at 12:35
  • 2
    Ah, looks like the language was retroactively changed via defect report http://wg21.link/cwg1299, so C++11 now also works the "new" way. (The DR says that previously only prvalues were considered "temporaries", and the new rules are more permissive.) – Kerrek SB May 26 '18 at 12:44
  • @KerrekSB So it's dangling reference until cpp11? – choxsword May 26 '18 at 12:46
  • 2
    @bigxiao: It used to be a dangling reference in C++11, too, until July 2017, when it became valid C++11. The way in which defect reports apply to older revisions of the standard is very murky, since officially they only apply the one and only current standard. Each publication supersedes the previous one. The wording of the DR cannot literally apply to C++03, which didn't have glvalues, but your vendor may choose to also extend lifetime in C++03 mode now based on this DR. That would be a reasonable interpretation I think. – Kerrek SB May 26 '18 at 12:48
  • It looks like the behavior depends on which C++ standard you are using. I recommend using C++17, but if that is too bleeding edge then C++14. – Eljay May 26 '18 at 12:56
  • You might want to also add a diagnostic on the default and copy constructors, and include `this` in your diagnostic. It should prove very informative. – Sam Varshavchik May 26 '18 at 13:02
  • @SamVarshavchik I cannot get your point, could you please provide an code example? – choxsword May 26 '18 at 13:04
  • You are printing a message in your destructor. Simply print another message in your default and copy constructors. Including `this`, in your message will let you see exactly which specific instances of your class get created and destroyed, and when. – Sam Varshavchik May 26 '18 at 13:06
  • Here's an example: On an old GCC: https://wandbox.org/permlink/N36VET6Vo2ZvcGpF. Then switch to a newer GCC to see the change in behaviour. Note that the CWG issue has been under discussion for a long time, it's only that it was officially accepted last year. But vendors seem to have implemented it for a long time already. – Kerrek SB May 26 '18 at 13:12
  • @KerrekSB Why is it dangling in earlier cpp11, that is, where does the cpp17 change the earlier cpp11? – choxsword May 26 '18 at 13:16
  • 2
    @bigxiao: Because of the way defect reports work. Note that it's strictly speaking also still dangling in earlier C++17s -- C++17 as published contains the old behaviour; the defect report was only accepted after C++17 was completed (e.g. see http://wg21.link/N4659). But "the standard" means "the published document *together with* all subsequently accepted defect reports". – Kerrek SB May 26 '18 at 14:26
  • Is an expression ever a temporary? – curiousguy May 28 '18 at 23:22

1 Answers1

8

According to the newest draft [class.temporary]/6 (irrelevant part is elided by me):

The third context is when a reference is bound to a temporary object. The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:

  • ...

  • a const_­cast ([expr.const.cast]), static_­cast ([expr.static.cast]), dynamic_­cast ([expr.dynamic.cast]), or reinterpret_­cast ([expr.reinterpret.cast]) converting, without a user-defined conversion, a glvalue operand that is one of these expressions to a glvalue that refers to the object designated by the operand, or to its complete object or a subobject thereof,

  • ...

... [ Note: An explicit type conversion ([expr.type.conv], [expr.cast]) is interpreted as a sequence of elementary casts, covered above. [ Example:

const int& x = (const int&)1;  // temporary for value 1 has same lifetime as x

— end example ] — end note ]

Your code is well-formed.


Before C++14, the wording in the standard is unclear about such case, and there is a defect issue 1376. This issue clarifies the lifetime of the temporary object should not be extended in such case. However, this clarification is superseded by issue 1299 (whose resolution is not included even in C++17, but in the current draft).

So you can conclude that before the resolution of issue 1299, it is a bug for GCC with version after 4.6. There is also a bug report 52202 for GCC.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • What about `auto&& ref=std::forward(T{});`? Is `ref` dangling? – choxsword May 26 '18 at 14:34
  • 2
    @bigxiao Not extended. Because none of the rules in [class.temporary]/6 apply. – xskxzr May 26 '18 at 14:38
  • @bigxiao The rule of extension of the lifetime of a temporary is a bunch of very special cases, if you step out of these narrowly defined case, you aren't covered. (It's too tricky if you ask me.) – curiousguy Jun 09 '18 at 14:32