c++referenceconstantstemporarytemporary-objects

const reference to temporary oddity


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 consts and just directly printed out a 10? Which aspect of the rule am I missing?


Solution

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