C++ Temporary Lifetime Extension With Const References

In C++, temporary objects normally destruct at the end of the full-expression in which they are created. However, the C++ standard provides a mechanism where binding a temporary object to a const lvalue reference (or an rvalue reference) extends the temporary's lifetime to match the lifetime of the reference itself. This article covers the specific conditions required for this extension to occur, how it applies to sub-objects, and the critical edge cases where lifetime extension fails.

Core Conditions for Lifetime Extension

For lifetime extension to take place, the temporary object must satisfy the following criteria:

  1. Direct Initialization of a Reference: The temporary must be directly bound to a reference to const (or a const&& / && rvalue reference) during the reference's declaration.

    const std::string& ref = std::string("Hello"); // Lifetime extended to match 'ref'
  2. Local Scope Association: The reference must be an automatic (local) variable, a namespace-scope variable, or a static variable. The temporary persists until the reference goes out of scope or the program terminates.

Extension with Sub-Objects and Member Access

Lifetime extension recursively applies when referencing a sub-object of a temporary:

  • Direct Member Access: If a const reference binds to a public data member of a temporary class or aggregate, the lifetime of the entire containing temporary object is extended.

    struct Pair { int a; std::string b; };
    const std::string& ref = Pair{1, "Data"}.b; // Entire Pair object's lifetime is extended
  • Array Elements: If an array temporary is indexed, binding a reference to an element extends the lifetime of the entire temporary array.

Conditional Operator Exceptions

When using the ternary conditional operator (?:), the lifetime of a temporary operand is extended if the conditional expression evaluates to that temporary and binds to a reference:

const std::string& ref = condition ? std::string("A") : std::string("B");
// Whichever temporary is selected has its lifetime extended to match 'ref'

Situations Where Lifetime Extension Does Not Apply

The C++ standard explicitly defines boundaries where lifetime extension does not occur, often leading to dangling references:

  1. Function Parameters: A temporary passed by const& to a function has its lifetime extended only until the end of the full-expression evaluating the function call. It does not persist within the function beyond that call.
  2. Function Return Values: Returning a reference to a temporary created inside a function does not extend the temporary's lifetime outside the function scope. The object is destroyed upon function exit, returning a dangling reference.
  3. Class Member Initializers: Binding a temporary to a reference member within a constructor's member initializer list does not extend the temporary's lifetime to match the containing class instance. The temporary is destroyed immediately upon completion of the constructor, leaving the member reference dangling.
  4. Dynamic Allocation (new): In a new expression, binding a temporary to a reference member of the dynamically allocated object extends the temporary only until the end of the full-expression containing the new operator, not until the object is deleted.
  5. Transitive Function Calls: If a temporary is passed to a function that returns a reference to that temporary, binding the result to a const reference does not extend the original temporary's lifetime; it destructs at the end of the statement.