Skip to content

76 Advanced The Hundred

Looping Over a Corpse

two loops, one pair of parentheses apart

#include <iostream>
#include <string>

struct Widget {
    std::string name_ = "the holy grail of C++ gotchas";
    const std::string& name() const { return name_; }
};

Widget makeWidget() { return Widget{}; }

int main() {
    std::cout << "getter: ";
    for (char c : makeWidget().name())
        std::cout << c;

    std::cout << "\nmember: ";
    for (char c : makeWidget().name_)
        std::cout << c;
    std::cout << '\n';
}

Run it. Do both loops print the name?

Answer

Only the second. The getter loop is undefined behavior — typical output (GCC on x86-64) is 16 bytes of garbage that change every run, then f C++ gotchas: the tail of a string that no longer exists. The member loop prints the name perfectly.

Why

Range-for desugars to auto&& __range = <your-expression>;, evaluated exactly once before the first iteration. makeWidget().name() returns a reference into the Widget temporary, and a reference handed back by a function call earns no lifetime extension — the Widget dies at the end of that hidden declaration, so the loop walks a destroyed string: undefined behavior. In the typical output the freed buffer's first bytes have been recycled as allocator bookkeeping; with a short enough name (SSO) it may even appear to work. The member version is the devious twin: binding a reference directly to a member of a temporary extends the whole temporary's lifetime to match the reference, so the second Widget outlives its entire loop.

GCC 11's -Wall -Wextra is completely silent here. C++23 (P2718) fixes the loop itself — every temporary in the range expression now lives as long as the loop — but GCC 11 predates it.

The fix

Name the owner first; a named object lives to the end of its scope:

auto w = makeWidget();
for (char c : w.name()) std::cout << c;

Takeaway: never feed a range-for an expression that returns a reference into a temporary — name the owner, then loop.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry