cplusplus / CWG

Core Working Group
24 stars 7 forks source link

CWG2737 [expr.prim.lambda] Lifetime extension via reference init capture #321

Open tomaszkam opened 1 year ago

tomaszkam commented 1 year ago

Full name of submitter (unless configured in github; will be published with the issue): Tomasz Kamiński

Reference (section label): expr.prim.lambda

Link to reflector thread (if any):

Issue description: Per strict reading of lambda [expr.prim.lambda] p6, the following code

struct Tracker {};
Tracker const create();
auto l = [&ref = create()] { return ref; }

Is equivalent to, with a lifetime of the ref being the same as the lifetime of the lambda:

auto& ref = create(); // Tracker const& is deduced, so this is well formed
auto l = [&ref] { return ref; }

As such per [class.temporary] p6, the lifetime of temporary returned by create() shall be extended to the lifetime of ref, which is the same as the lifetime of l.

We seem to require that for the following code, the lifetime of the temporary bound to ref is required to be extended beyond the stack frame of foo, and match the lifetime of l.

auto foo() {
    return [&ref = create()] { return ref; };
};
auto l = foo();
l();

The exception from [class.temporary] p6.11, does not apply as temporary is bound to the invented variable of reference type, not the return value (lambda object). And the lifetime of this invented variable is the same as the lifetime of l.

There seems to be implementation divergence and (https://godbolt.org/z/cnvWKTEzP)

Suggested resolution: Make bind a reference to temporary in the init-captures ill-formed, even if the const value of user-defined type is created. All such cases can be replaced with [x = create()].

languagelawyer commented 1 year ago

Per strict reading of lambda [expr.prim.lambda] p6

You mean [expr.prim.lambda.capture]/6?

Is equivalent to

[expr.prim.lambda.capture]/6 says

An init-capture without ellipsis behaves as if it declares and explicitly captures a variable of the form “auto init-capture ;”, except that…

But it does not say «declares in the scope where the lambda-expression appears». I do not think it is even possible to inject a variable declaration into any scope where a lambda-expression can appear (like, NSDMI?)

I think, like in other cases of inventing variable declaration (e.g. in static_cast<R&>), «behaves as if it declares a variable» means only «the implicit conversions to be applied are determined as if initializing a variable»; or, in case of lambdas, the type of the capture is determined as if by auto deduction for a variable.

tomaszkam commented 1 year ago

You mean [expr.prim.lambda.capture]/6?

Yes.

But it does not say «declares in the scope where the lambda-expression appears». I do not think it is even possible to inject a variable declaration into any scope where a lambda-expression can appear (like, NSDMI?)

I fully agree, this variable exists outside of the source code, however, the wording for reference binding is not limited by the scope of the variable, and is defined in terms of lifetime, which we painstakingly define.

So it should trigger unless we say otherwise. And it seems to indicate, that the initializer of ref-init capture of lambda placed in a return statement, is lifetime extended to the lifetime of this invented variable, which is the same as the closure object. And if we return by the value this lambda lifetime is defined by the callee.

I think, like in other cases of inventing variable declaration (e.g. in static_cast<R&>), «behaves as if it declares a variable» means only «the implicit conversions to be applied are determined as if initializing a variable»; or, in case of lambdas, the type of the capture is determined as if by auto deduction for a variable.

I do not think that this argument applies here, as we refer to their odr-uses:

jensmaurer commented 1 year ago

CWG2737

frederick-vs-ja commented 1 year ago

Given the current direction is not to allow binding to a temporary, it seems that we can strike the requirement for the lifetime of the hypothetical reference variable, which only matters on lifetime extension.