Kona resolves C++23 ballot comments and repairs range-for lifetimes
WG21's hybrid Kona meeting resolves 92 national-body comments. A range-for lifetime repair shows how final review can change ordinary code without reopening the revision's feature set.
Replacing direct member access with a getter should not quietly make a range-based loop use an object after destruction. Yet C++'s temporary-lifetime rules can produce that result when the getter returns a reference into a temporary owner. WG21's Kona meeting addresses this defect while resolving comments on the C++23 committee draft.
In his November 12 trip report, Herb Sutter reports final resolutions for 92 of 137 national-body comments, leaving 45. He says no features were added or removed. The work concerns corrections and completion of the draft, including a repair whose visible consequence is that familiar range-for expressions become safe under the revised rules.
RAII is my strongest reason to value C++, so rules that determine destructor timing deserve particular attention. Scope-based cleanup is useful when the scope is understandable. A loop that implicitly destroys its owner before iteration begins makes a simple control structure depend on lifetime details its syntax does not expose.
A reference to an element does not own the container
The report's example contrasts iterating a returned vector with iterating its first string. A range-for over createStrings() can retain the temporary vector. A loop over createStrings().at(0) instead obtains a reference to an element of the temporary. Under the old rules, that reference does not keep the vector alive through the loop.
The earlier P2012R2 analysis explains this through the loop's expansion. A hidden range reference receives the range-initializer expression. Iterator setup and the actual loop occur afterward. Temporaries whose lifetimes are not extended disappear at the end of the initializer's full-expression.
In the getter case, the hidden reference binds to the reference returned by a function call. It does not automatically propagate ownership back to every temporary involved in producing that reference. The element remains a valid reference type while its referent's owner has already been destroyed. Subsequent iteration then uses invalid storage.
Direct access to a member can follow a different lifetime-extension rule. P2012R2 shows how changing that direct member expression into a getter call introduces the defect without changing the apparent collection the loop visits. This is a source-level usability problem as well as a memory-safety issue.
The correction extends the relevant temporaries
P2644R1, dated November 11, supplies the focused repair discussed in Sutter's report. A temporary created in the range-for initializer, other than a function parameter object, persists through the statement if it would otherwise be destroyed at the end of that initializer's full-expression.
The paper states that these objects are destroyed after the loop's declared variables, in reverse construction order. This places their cleanup after the period during which the loop needs them. It changes destructor timing, which is why the paper includes a compatibility discussion instead of presenting the rule as having no observable consequences.
The correction covers more than vectors. P2644R1 lists examples involving optional values, variants, tuples, and spans that refer into temporary storage. The shared issue is the relationship between the object being iterated and temporary owners used to construct its range expression.
The function-parameter exclusion matters. The paper contrasts a function returning a reference to an argument bound through a const reference with a function returning a reference to its own by-value parameter. Extending the caller's relevant temporary cannot repair a callee that returns a reference to its dead parameter object. The new rule does not make arbitrary dangling references valid.
Existing code can express the owner explicitly
For code using earlier rules, giving the owner a name before accessing its elements avoids this particular hidden lifetime problem. A range-for init-statement can keep that ownership within the loop's scope.
#include <string>
#include <vector>
std::vector<std::string> create_strings();
void inspect_first()
{
for (auto strings = create_strings(); char c : strings.at(0)) {
(void)c;
}
}
The vector named strings remains in scope for iteration. If it is empty, at(0) reports that through its ordinary checked-access behaviour. The example addresses lifetime by naming the owner, while leaving selection of the first element as a separate operation with its own requirements.
The proposal authors argue that workarounds do not solve the language's teaching problem. A programmer must first know the expression is dangerous before choosing the longer form. P2644R1 therefore focuses on improving the default loop semantics, with explicit ownership still available when code needs particular lifetime boundaries.
I prefer that outcome for a control structure recommended for ordinary iteration. The loop should retain the temporary data it naturally appears to use. Explicitly named ownership remains valuable, especially when destruction releases something more consequential than container memory.
Later destruction is an observable change
P2644R1 acknowledges that extending a temporary can delay its destructor. That can matter when destruction releases a lock or another resource the loop body wants to acquire. The paper includes an example involving locked access to illustrate the general compatibility issue.
This is a direct consequence of RAII, not a separate exception to it. Keeping an object alive keeps its owned resources alive too. A lifetime repair must specify when destruction occurs so programs depending on that release can reason about the new boundary.
Hybrid meetings support final review
Kona was the first in-person WG21 meeting since Prague in February 2020, according to Sutter. It was also the committee's first hybrid meeting with remote participation available to every subgroup that met. More than 160 people attended, approximately two-thirds on site and one-third remotely.
Sutter describes six days of meetings, with nine of the committee's 26 active subgroups meeting in six parallel tracks. Remote presentations and comments participated in the work. Those details explain the meeting's format without implying that every active subgroup met or that every ongoing proposal reached a decision.
The report distinguishes this C++23 work from ongoing proposals for later revisions. Contracts and execution continued in parallel discussions, but that did not mean those facilities entered the C++23 feature set. The remaining national-body comments still require resolution before technical work is complete. Kona's range-for correction provides a concrete example of what that final review can accomplish within an already bounded revision.