c++referencelifetimetemporarytemporary-objects

Returning const reference to temporary behaves differently than local const reference?


I'm trying to get a better grasp of how lvalues and rvalues are dealt with as references, so I created this toy example:

#include <iostream>

struct Val
{
    Val(int num) : num(num){};
    ~Val()
    {
        std::cout << "Destructing with value " << num << std::endl;
    }

    int num;
};

const Val &test(const Val &val)
{
    return val;
}
int main()
{
    std::cout<< "Creating foo with value 5" <<std::endl;
    const Val &foo = test(Val(5));
    std::cout<< "Creating bar with value 3" <<std::endl;
    const Val &bar(3);
    std::cout<< "Finishing main function" <<std::endl;
    return 0;
}

This prints out:

Creating foo with value 5
Destructing with value 5
Creating bar with value 3
Finishing main function
Destructing with value 3

Essentially we're seeing this rvalue Val(5) bind to const reference parameter val in function test, and that same value returned — however, the destructor gets called immediately as it's a temporary. But when we try constructing Val(3) and assigning to a const reference, it remains in scope for the entire block.

I was under the conception that we can bind rvalues to const references and that'll extend their lifetime until that reference goes out of scope, but that seems to not necessarily be the case here. I'd appreciate any insight into where I'm misunderstanding.


Solution

  • Given const Val &foo = test(Val(5));, the temporary Val(5) will be destroyed after the full expression immediately, its lifetime won't be extended to the lifteime of the reference foo. It's not bound to foo directly, but bound to the reference parameter of test.

    In reference initialization,

    (emphasis mine)

    Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference, with the following exceptions:

    • a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference.

    In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime.