We all know that things like this are valid in c++:
const T &x = T();
while:
T &x = T();
is not.
In a recent question the conversation lead to this rule. The OP had posted some code which clearly evokes UB. But I would have expect a modified version of it to work (This is the modified version):
#include <iostream>
using namespace std;
class A {
public:
A(int k) { _k = k; };
int get() const { return _k; };
int _k;
};
class B {
public:
B(const A& a) : _a(a) {}
void b() { cout << _a.get(); }
const A& _a;
};
B* f() {
return new B(A(10));
}
int main() {
f()->b();
}
This prints garbage on some machines, 10 on others... sounds like UB to me :-). But then I thought, well A
is basically a glorified int
all it does it initialize one and read it. Why not just call A
an int
and see what happens:
#include <iostream>
using namespace std;
typedef int A;
class B {
public:
B(const A& a) : _a(a) {}
void b() { cout << _a; }
const A& _a;
};
B* f() {
return new B(A(10));
}
int main() {
f()->b();
}
It prints 10
every time. It at least seems like the const reference rule is in effect for the int
version, but not for the class version. Are they both simply UB due to the use of the heap? Am I just lucky with the int
version because the compile saw through all const
s and just directly printed out a 10
? Which aspect of the rule am I missing?
It simply demonstrates that analyzing language behavior by "trying it in the compiler" doesn't normally produce any useful results. Both of your examples are invalid for the very same reason.
The lifetime of the temporary is only extended when you use that temporary as the direct initializer for a const reference - only that will establish a "lifetime" link between the reference and the temporary.
Trying to pass a temporary as a constructor's argument and attaching a const reference inside the constructor will not establish the aforementioned link and will not extend the lifetime of the temporary.
Also, in accordance with C++ standard, if you do this
struct S {
const int &r;
S() : r(5) {
cout << r; // OK
}
};
the lifetime of the temporary is only extended to the end of the constructor. Once the constructor is finished, the temporary dies, meaning that this
S s;
cout << s.r; // Invalid
is invalid.
Your experiment with int
simply "appears to work", purely by accident.
You just got lucky. Changing B::b to this:
void b() {
int i = rand();
int j = rand();
cout << _a << endl;
}
prints out random numbers.
It prints 10 every time.
Modify the main function a little and it won't print 10 anymore:
int main()
{
B* p = f();
cout << "C++\n"; // prints C++
p->b(); // prints 4077568
}
how does this link gets established at what level?
See 12.2 [class.temporary] §4 and §5:
Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created.
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is [...]
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: [...]
A temporary bound to a reference parameter in a function call persists until the completion of the full-expression containing the call.
So in your case, the temporary is destroyed after the evaluation of the full-expression new B(A(10))
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With