Life extension of temporary by const reference - c++

C++
I'm trying to see how const references prolong the lifetime of temporaries. I'm running the code from the snippet in one of the answers to What are the differences between pointer variable and reference variable in C++? and got conflicting results between VC11 and g++ 4.8. I've expanded the snippet here:
#include <stdio.h>
struct scope_test
{
~scope_test() { printf("scope_test done!\n"); }
};
int main()
{
const scope_test& test = scope_test();
printf("in scope\n");
}
The answerer got the result:
in scope
scope_test done!
I tried it in VC11 and got this:
scope_test done!
in scope
scope_test done!
I assumed the VC11 result was caused by a lack of copy elision, so I tried to see if disabling copy elision on g++ with fno-elide-constructors would give the same result as VC11. (I don't think copy elision can be toggled in VC11.) But, g++ gives the answerer's result regardless of the setting of the flag.
The C++11 Standard, ISO/IEC 14882:2011(E), §12.2/4 and /5 states:
There are two contexts in which temporaries are destroyed at a
different point than the end of the full-expression...
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:
...
Does the VC11 result have anything to do with copy elision? Is it a VC11 bug?
The answerer states:
temporaries assigned to const references live until the const
reference goes out of scope
The list of exceptions to §12.2/5 don't rule out a non-const reference. What am I missing from the Standard?
Removing the const in VC11 produces the same result as the VC11 one with the const. Removing the const in g++ gives error: invalid initialization of non-const reference of type ‘scope_test&’ from an rvalue of type ‘scope_test’. Why is there a difference?
EDIT:
I added copy and move constructors and tried:
#include <stdio.h>
struct scope_test
{
scope_test() { printf("regular ctor\n"); }
scope_test(const scope_test& src) { printf("copy ctor\n"); }
scope_test(scope_test&& src) { printf("move ctor\n"); }
~scope_test() { printf("scope_test done!\n"); }
};
int main()
{
const scope_test& test= scope_test();
printf("in scope\n");
}
Regardless of the toggling of copy elision, g++ gives:
regular ctor
in scope
scope_test done!
VC11 gives the same thing, even if the const is removed. If the const is removed from g++, g++ still gives error: invalid initialization of non-const reference of type ‘scope_test&’ from an rvalue of type ‘scope_test’.

Both behaviors are correct, certainly according to the C++03 standard (8.5.3 [dcl.init.ref] paragraph 5):
Otherwise, the reference shall be to a non-volatile const type (i.e., cv1 shall be const). [Example: ...]
If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation-defined):
— The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.
— A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.
I think the definition of C++11 still allows the copy to be made but the wording doesn't as clearly allow the copy. In any case, VC++ doesn't claim to be fully C++11 compliant.

Related

Is a const reference bound to another reference which is cast from temporary a dangling reference?

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

const reference to temporary reference

#include <iostream>
using namespace std;
struct CL
{
CL()
{
cout<<"CL()"<<endl;
}
CL(const CL&)
{
cout<<"CL(const CL&)"<<endl;
}
~CL()
{
cout<<"~CL()"<<endl;
}
};
CL cl;
CL fnc()
{
return cl;
}
int main() {
cout<<"start"<<endl;
const CL& ref=static_cast<const CL&>(fnc());
//...Is "ref" valid here??
cout<<"end"<<endl;
return 0;
}
What's lifetime of temporary object returned by fnc()? Is it lifetime of "ref" or of temporary reference static_cast(fnc()), which destroyed at end of statement?
Output of gcc (lifetime of fnc() is lifetime of "ref"):
CL() //global object "cl"
start
CL(const CL&)
end
~CL()
~CL() //global object "cl"
Output of VS2013 (lifetime of fnc() is lifetime of temporary reference):
CL() //global object "cl"
start
CL(const CL&)
~CL()
end
~CL() //global object "cl"
What's correct by Standard?
I believe Visual Studio is correct here, this is covered in defect report #1376 which says:
In a declaration like
T&& r = static_cast<T&&>(T());
it is not clear what the lifetime of the T temporary should be.
According to 5.2.9 [expr.static.cast] paragraph 4, the static_cast is
equivalent to a declaration of an invented temporary variable t. The
lifetime of the temporary is extended to that of t, but it is not
clear what that lifetime should be, nor if the subsequent binding of t
to r would affect the lifetime of the original temporary. (See also
issue 1568.)
and the discussion includes this conclusion:
The reference is bound to the xvalue result of the static_cast, so the lifetime of the temporary is not extended and this example results in a dangling reference.
and defect report 1568 covers this case more specifically:
According to 12.2 [class.temporary] paragraphs 4-5,
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression...
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...
It is not clear whether this applies to an example like the following:
struct S { };
const S& r = (const S&)S();
and the response was:
This issue is a duplicate of issue 1376.
so in this case:
const CL& ref=static_cast<const CL&>(fnc());
the reference is bound to the result of the static_cast and not to CL and therefore CL is a dangling reference.
For reference the relevant text from the draft C++11 standard section 5.2.9 [expr.static.cast]:
Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_-
cast(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5). The
effect of such an explicit conversion is the same as performing the declaration and initialization and then
using the temporary variable as the result of the conversion. The expression e is used as a glvalue if and
only if the initialization uses it as a glvalue.

Do *non*-const references prolong the lives of temporaries?

Once upon a time, I assumed that code like this would fail:
const MyClass& obj = MyClass();
obj.DoSomething();
because the MyClass object would be destroyed at the end of its full-expression, leaving obj as a dangling reference. However, I learned (here) that this isn't true; the standard actually has a special provision that allows const references to keep temporaries alive until said references are destroyed themselves. But, it was emphasized, only const references have this power. Today I ran the code below in VS2012 as an experiment.
struct Foo
{
Foo() { std::cout << "ctor" << std::endl; }
~Foo() { std::cout << "dtor" << std::endl; }
};
void f()
{
Foo& f = Foo();
std::cout << "Hello world" << std::endl;
}
The output when calling f() was:
ctor
Hello world
dtor
So I had a look at the C++11 draft standard, and only found this (§ 12.2/4):
There are two contexts in which temporaries are destroyed at a
different point than the end of the full-expression. The first context [doesn't
apply]. 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.
The word const is conspicuously absent from the above. So; has this behavior been changed for C++11, was I wrong about the const thing to begin with, or does VS2012 have a bug and I just haven't found the relevant part of the standard?
The behavior hasn't changed, you just need to turn your warning level up to /W4. VisualStudio implements the lifetime extension rule even for non-const lvalue references as a compiler extension. In this context, binding an rvalue to the non-const reference behaves the same as if you were binding it to a const reference.
With /W4 you'd see this:
warning C4239: nonstandard extension used : 'initializing' : conversion from 'Foo' to 'Foo &'
1> A non-const reference may only be bound to an lvalue
The text disallowing binding of an rvalue to a non-const lvalue reference can be found in §8.5.3/5
— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference.[ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
int i = 2;
double& rd3 = i; // error: type mismatch and reference not const
—end example ]
The second half of the quoted statement is what allows binding of a temporary to an rvalue reference, as shown in litb's answer.
string &&s = string("hello");
This, combined with the lifetime extension rule in §12.2/5, means the lifetime of the temporary will now match the lifetime of the (rvalue) reference it is bound to.
The word const was never present in this section. The rule
has always been (from as long as I can remember) that
a temporary used to initialize a reference has its lifetime
extended to match that of the reference, regardless of the type
of the reference.
Sometime in the late 1980's (very pre-standard), C++ introduced
the rule that a temporary could not be used to initialize
a non-const reference. Initializing a non-const reference with
a temporary would still extend the lifetime (presumably), but
since you couldn't do it... Most compilers implemented
a transition period, in which such an initialization would only
emit a warning (and the lifetime was extended).
For some reason, when Microsoft finally decided to implement
C++ (some time in the early 1990's), they decided not to
implement the new rule, and allowed initialization of
a non-const reference with a temporary (without even a warning,
at a time when most other vendors were gradually turning the
warning into an error). And of course, the implemented the
usual lifetime rule.
Finally, in C++11, new types of references were introduced,
which allowed (or even required) initialization with
a temporary. The rule about the lifetime of temporaries hasn't
changed, though; a temporary which is used to initialize
a reference (regardless of the type of reference) has its
lifetime extended.
(With a few exceptions: I would not recommend using a temporary
to initialize a class member reference in an initialization
list.)
No, because rvalue references don't need to be const, so the Standard quote is correct
string &&s = string("hello");
The lifetime is still enlargened. The constraints that make the code invalid for non-const lvalue reference is at clause 8 (notice that it is not the right place to just add "const" and "rvalue reference" etc in the paragraph you quoted. You need an active rejection of such bindings, not just saying that the lifetime of such bindings are not enlarged because you would leave the binding itself still wellformed).

Understanding Rvalue Reference

Is this undefined behavior? If not, what is the behavior?
// In some external library with, say, header "a.h"
void f(int &&x) {
x = 5; // Which memory does this assignment apply to?
}
#include "a.h"
int main() {
f(7);
// At this point, where is the value of 5?
return 0;
}
C++11 8.5.3 describes initialization of references (including rvalue references).
It says:
Otherwise, a temporary of type “cv1 T1” is created and initialized
from the initializer expression using the rules for a non-reference
copy-initialization (8.5). The reference is then bound to the
temporary.
So, a temporary of type int is bound to the rvalue reference, and thrown away immediately after the call returns.
Section 12.2 (Temporaries) gives examples very similar to yours.

Is there a way to disable binding a temporary to a const reference?

In C++ it is possible to bind a temporary to a const reference:
struct A {};
int main() {
const A& a = A();
}
Is there any way to disable this for some particular class A so that it would be impossible to bind a temporary of this class to a const reference?
No, and if you need to do this, you're doing something else wrong.
In general there doesn't seem to be a way to do disable binding a temporary to a const reference.
However, to give a substantiated answer, I'd like to cite the C++ 2003 standard:
If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible
with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation-defined):
— The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within
that object.
— A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire
rvalue object into the temporary. The reference is bound to the temporary or to a sub-object
within the temporary.
93)
The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.
So it might seem that this can be achieved in C++03 by making a copy constructor private:
struct A {
A() {}
private:
A(const A&);
};
int main() {
const A& a = A();
}
However, this will not work with popular compilers. For example, GCC accepts the above code even with -std=c++03 flag. Clang also accepts this code, but with a warning:
test.cc:8:12: warning: C++98 requires an accessible copy constructor for class 'A'
when binding a reference to a temporary; was private
So contrary to the standard there is no way to do this.
C++11 no longer require an accessible copy constructor in this case.