When `const auto& r = cond ? a : b;` Becomes a Dangling Reference – 3 C++ Lifetime‑Extension Pitfalls
The article explains that although C++ extends the lifetime of temporaries bound to a const reference, this rule has three critical edge cases—returning const references from functions, binding references in member initializer lists, and using the ternary operator—each of which can produce a dangling reference and cause obscure runtime crashes.
C++ permits binding a temporary object to a const reference, which extends the temporary's lifetime until the reference itself goes out of scope. While this rule is widely known, most developers overlook three fatal boundary conditions where the extension does not apply, turning the reference into a dangling one that can cause crashes or data corruption.
Rule Overview: What "Lifetime Extension" Means
A temporary object normally lives until the end of the full‑expression. If a const lvalue or rvalue reference is directly bound to that temporary (as specified in [class.temporary] p6), the temporary's lifetime is extended to match the reference's lifetime.
const std::string& ref = std::string("hello"); // safe, ref lives as long as the scopeThe keyword "directly bound" is the crux: the reference must be initialized by the same expression that creates the temporary, without any intermediate steps.
Boundary One – References Returned from Functions Do Not Extend
Returning a const T& from a function and binding it to another const auto& does not extend the temporary's lifetime because the binding occurs in the caller, while the temporary is created inside the callee.
const std::string& get_name() {
return std::string("temporary"); // temporary destroyed after return
}
const std::string& name = get_name(); // dangling reference!The standard deliberately cuts the extension chain at function boundaries to avoid the high cost of whole‑program analysis.
Boundary Two – Reference Members in Constructors
When a temporary is bound to a reference member in a constructor's member‑initializer list, the temporary is destroyed at the end of the constructor body, not when the object itself is destroyed.
struct Holder {
const std::string& ref;
Holder(const std::string& s) : ref(s) {}
};
Holder h(std::string("temporary")); // ref dangles after constructionThus, reference members are a red flag in code reviews unless the referenced object's lifetime is provably longer.
Boundary Three – The Ternary (Conditional) Operator
The conditional operator must produce a single value category. If one branch yields an lvalue and the other a prvalue, the compiler materializes a temporary for the lvalue branch, making the whole expression a prvalue. Binding a const reference to that result can be safe or unsafe depending on the condition.
const std::string& label = use_default ? default_label : make_label();When use_default is false, make_label() returns a prvalue; the reference binds directly and the lifetime is extended. When true, the lvalue default_label is copied into a temporary before binding, which is also safe. However, more complex cases—such as casting a prvalue to a const std::string& —break the direct‑binding chain, leaving a dangling reference:
const std::string& result = use_cache
? get_cached(key)
: static_cast<const std::string&>(compute(key)); // dangling if falseCompilers may perform implicit casts similar to the explicit static_cast, making the problem invisible in source code.
Three Ironclad Rules to Avoid Dangling References
Prefer receiving the conditional result by value rather than by reference:
auto label = use_default ? default_label : make_label(); // safe copyIf the two branches have different types or value categories, avoid initializing a reference with the ternary operator; use an explicit if‑else block instead.
const std::string& label = [&](){
if (use_default) return default_label;
static thread_local std::string cached = make_label();
return cached;
}();Enable compiler warnings: GCC -Wdangling-reference (GCC 13+) and Clang -Wdangling / -Wreturn-stack-address catch many of these patterns.
Why the Rule Exists
The lifetime‑extension rule dates back to C++98 as a zero‑overhead abstraction that simplifies common patterns (e.g., binding a temporary string to a const reference). CWG 1299 clarified that the extension applies only to the temporary directly bound by the reference, and CWG 86/446 defined the behavior for the conditional operator. The rule deliberately stops at function calls, member initializers, and implicit conversions to keep the language implementable without full‑program analysis.
In practice, think of lifetime extension as a "one‑line" guarantee: the reference must be initialized on the same line by the temporary itself. Any intermediate step—function call, static_cast, member initializer, or conditional conversion—breaks the chain and re‑introduces manual memory‑safety responsibilities.
This article is based on authoritative references and has been carefully reviewed for technical accuracy.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
