Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime extension of temporary by non-const reference using const-cast

This is something that came up recently and which I feel shouldn't work as it apparently does:

#include <iostream>
#include <memory>

int main()
{
    std::shared_ptr<int>& ptr = const_cast<std::shared_ptr<int>&>(
        static_cast<const std::shared_ptr<int>&>(
            std::shared_ptr<int>(
                new int(5), [](int* p) {std::cout << "Deleting!"; *p = 999;  delete(p); }
            )
        )
    );
    std::cout << "I'm using a non-const ref to a temp! " << *ptr << " ";
}

The use of shared_ptr isn't necessary here, but the custom deleter allows for an easy demonstration of the lifetime of the resulting object. There resulting output from Visual Studio, Clang and GCC is the same:

I'm using a non-const ref to a temp! 5 Deleting!

Meaning the lifetime of the resulting shared_ptr has, through some mechanism, been extended to match that of the std::shared_ptr<int>& ptr.

What's Happening?

Now, I'm aware that the lifetime of a temporary will be extended to that of the reference for the case of a constant reference. But the only named object is a non-const reference, all other intermediate representations I would expect to have a lifetime equal only to the initialization expression.

Additionally, Microsoft have an extension which allows non-const references to extend the lifetime of a bound temporary, but this behaviour appears to be present even when that extension is disabled and, additionally, also appears in Clang and GCC.

According to this answer I believe the temporary is implicitly being created as const, so attempting to modify the object referenced by ptr is probably undefined behaviour, but I'm not sure that knowledge tells me anything about why the lifetime is being extended. My understanding is that it is the act of modifying a const that is UB, not simply taking a non-const reference to it.

My understanding of what should be happening is as follows:

  1. Type() creates a prvalue with no cv-specification.

  2. static_cast<const Type&>(...) materializes that prvalue into a const xvalue with a lifetime equal to the interior expression. We then create a const lvalue reference to that const xvalue. The lifetime of the xvalue is extended to match that of the const lvalue reference.

  3. const_cast<Type&>(...) produces an lvalue reference which is then assigned to ptr. The const lvalue reference then expires, taking the materialized xvalue with it.

  4. I try to read dangling reference ptr and bad things happen.

What's wrong in my understanding? Why don't the bits in italics happen?

As an extra bonus question, am I correct in thinking that the underlying object is const, and that any attempt to modify it through this path will result in undefined behaviour?

like image 270
Steelbadger Avatar asked Dec 04 '19 12:12

Steelbadger


People also ask

Does const reference extend lifetime?

In the by-reference case, we get a const Base& reference that refers to a Derived object. The entire temporary object, of type Derived , is lifetime-extended.

How can you extend the lifetime of an object?

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.

How to declare const reference in c++?

A variable can be declared as a reference by putting '&' in the declaration. int i = 10; // Reference to i. References to pointers is a modifiable value that's used same as a normal pointer.

Why reference and const?

Because the reference is a const reference the function body cannot directly change the value of that object. This has a similar property to passing by value where the function body also cannot change the value of the object that was passed in, in this case because the parameter is a copy.


1 Answers

Any reference can extend the lifetime of an object. However, a non-const reference cannot bind to a temporary as in your example. The Microsoft extension you refer to is not "Extend lifetime by non-const references," rather "Let non-const references bind to temporaries." They have that extension for backward compatibility with their own previous broken compiler versions.

By a cast you have forced the binding of a non-const reference to a temporary, which does not appear to be invalid, just unusual because it cannot be done directly. Once you've accomplished that binding, lifetime extension occurs for your non-const reference the same as it would for a const reference.

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

like image 57
John Zwinck Avatar answered Oct 03 '22 08:10

John Zwinck