Repository navigation
CWG3043 [class.temporary] Range extension for enumerating expansion statements #724
Description
Activity
The general idea of this for-range "temporary extension" rule is that temporaries created in the initializer are lifetime-extended for the invented "auto&& range = init" statement.
I appreciate this doesn't really fit the enumerating case here, in particular when initializing a non-reference variable (we expect a copy before anything can dangle in the usual cases).
It could be even
struct T { int t; }; struct S { const T *s; }; S foo (const T &x) { S s {&x}; return s; } struct U { const int *s; }; U bar (const int &x) { U u {&x}; return u; } void baz () { template for (auto a = { foo ({ 42 }), bar (42) }) { auto b = *a.s; } }
with no references at all, and the question is if it should be lifetime extended even in that case (until end of the compound-statement).
So one possible wording for the enumerating case could be:
"If such a temporary object would otherwise be destroyed at the end of the ui full-expression, the object persists for the lifetime of the for-range-declaration."
but it would need to link to the ui from [stmt.expand]/(5.3) (at least if it should be destructed for each "iteration" separately at the end of that "iteration".Your example would also be a trap if someone wrote
auto x = foo({42});in regular code, so I'm not sure we want to prevent anything from dangling here.If (big if) we lifetime-extend anything in this case, we certainly should destroy at the end of the corresponding "iteration".
Sure, and so does the case with & members. One possibility is of course to extend only if for-range-declaration has a reference type. In the stmt.ranged case
auto &&range = for-range-initializer ;always has.Yes, but in the [stmt.range] case (and the other expansion statement cases), we have an expression that describes the entire range (and it's surprising those temporaries don't survive). For the enumerating expansions, we evaluate the values for the individual "iterations" separately, so there is no "covers the entire range" situation that would be surprising. I think. CWG issue time.
Also, wonder what is the point of having the fifth case talk about iterating expansion statements. In that case expansion-initializer is always used in
static constexpr auto&& range = expansion-initializer ;
and so has to be a constant expression, so I don't see how something would need to be lifetime extended in that case.
For destructuring expansion statements sure, those don't have to be constexpr, so lifetime extension makes sense there.- changed the title
[-][class.temporary] Range extension for enumerating expansion statements[/-][+]CWG3043 [class.temporary] Range extension for enumerating expansion statements[/+]on Jul 27, 2025 in the [stmt.range] case (and the other expansion statement cases), we have an expression that describes the entire range (and it's surprising those temporaries don't survive). For the enumerating expansions, we evaluate the values for the individual "iterations" separately, so there is no "covers the entire range" situation that would be surprising. I think.
This argument suggests that destroying temporaries created in one element of the expression-list before the next element is evaluated would not be surprising, but it doesn't argue for either side of "should we lifetime-extend until the end of the iteration".
As for that, one of the points that was brought up in EWG when discussing lifetime extension in the range-based for loop was the fact that, even though
auto&& r = foo().bar()doesn't lifetime-extend thefoo()temporary (and we expect the user to be aware of this and act accordingly), the range-based for loop case is special because the syntax doesn't explicitly show you the reference initialization. To restate in Core lingo, the boundaries of the full-expression in whichfoo().bar()is evaluated are not made obvious to the user. This makes the syntax a footgun.The same logic applies to the enumerating expansion statement: the user is not actually shown the full-expression in which each element of the list is evaluated, so if we don't lifetime-extend the temporaries until the end of the corresponding iteration then it's going to be a footgun.
Full name of submitter (unless configured in github; will be published with the issue): Jakub Jelinek
Reference (section label): [class.temporary]
Link to reflector thread (if any):
Issue description: P1306R5 adjusted the fourth context bullet so that it applies also to enumerating expansion statements.
The rest of the bullet talks about for-range-initializer which doesn't apply to enumerating expansion statement and references, but in enumerating expansion statements there might be no references.
"The fourth context is when a temporary object is created in the for-range-initializer of either a range-based for statement or an enumerating expansion statement ([stmt.expand]). If such a temporary object would otherwise be destroyed at the end of the for-range-initializer full-expression, the object persists for the lifetime of the reference initialized by the for-range-initializer."
So, I think enumerating expansion statement temporary extension should probably be handled in a separate bullet and make it clear what is being extended and in which cases.
Consider
For S1 this adds auto a = foo ({ 42 }); declaration before instantiation of the compound-statement. a is not a reference, in this case it contains a reference non-static data member, but it could very well be e.g. a pointer as well. Is the intent that the temporary is only destroyed at the end of the compound-statement, regardless if for-range-declaration has reference type or not?
Suggested resolution: