CWG Issue 2569 (original) (raw)
This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 118f. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2025-11-07
2569. Use of decltype(capture) in a lambda's parameter-declaration-clause
Section: 7.5.5.2 [expr.prim.id.unqual]Status: CD6Submitter: Barry RevzinDate: 2022-04-16Liaison: EWG
[Accepted at the July, 2022 meeting as part of paper P2579R0 (Mitigation strategies for P2036 "Changing scope for lambda trailing-return-type").]
Paper P2036R3disallowed using captures in the _parameter-declaration-clause_of a lambda, because it is not yet known at that point whether the lambda is going to be mutable, and thus the type of an expression referring to a capture may or may not be const. Such problematic uses of captures are now ill-formed. The paper was approved as a Defect Report, recommending to implementers to apply the change to all language modes back to C++11.
However, that broke legitimate uses in popular implementations of the standard library such as:
local_end it) { return it != local_end; };
As specified in 9.2.9.6 [dcl.type.decltype] bullet 1.3,decltype(local_end) does not depend on whether the lambda ends up being mutable or not:
- otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access (7.6.1.5 [expr.ref]),decltype(E) is the type of the entity named by E. If there is no such entity, the program is ill-formed;
Possible approaches (not necessarily exclusive):
- Redesignate paper P2036R3 as a C++23 feature, not a Defect Report.
- Carve out an exception for such and similar uses of captures, e.g. decltype(x) or sizeof(x) are acceptable when x names a capture.
- Limit the lookup change effected by P2036R3 to the trailing-return-type (option 1 in the list presented insection 4.1 of P2036R3).
- Accept as much existing code as possible by requiring to check for mutable before parsing the grammatical elements preceding the location where mutable can appear. This approach might work for the attribute-specifier-seq and the parameter-declaration-clause, but is a serious implementation hardship for the template-parameter-list and the first requires-clause. For example,[=]<X<decltype((a))>::a<0> may or may not be a complete template-parameter-list, depending on whether ...::ais a template.
Suggested resolution (carves out an exception fordecltype, sizeof, noexcept):
Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 3 as follows:
An unqualified-id is mutable-agnostic if it is the operand of a decltype(9.2.9.6 [dcl.type.decltype]), sizeof(7.6.2.5 [expr.sizeof]), or noexcept(7.6.2.7 [expr.unary.noexcept]). If the _unqualified-id_appears in a lambda-expression at program point P and the entity is a local entity (6.1 [basic.pre]) or a variable declared by an init-capture (7.5.6.3 [expr.prim.lambda.capture]), then let S be the compound-statement of the innermost enclosing lambda-expression of P. If naming the entity from outside of an unevaluated operand within S would refer to an entity captured by copy in some intervening lambda-expression, then let E be the innermost such lambda-expression, and:
- If the unqualified-id is mutable-agnostic or P is in E's function parameter scope but not its parameter-declaration-clause, then the type of the expression is the type of a class member access expression (7.6.1.5 [expr.ref]) naming the non-static data member that would be declared for such a capture in the object parameter (9.3.4.6 [dcl.fct]) of the function call operator of E. [Note 3: If E is not declared mutable, the type of such an identifier will typically be const qualified. —_end note_]
- Otherwise (if the unqualified-id is not mutable-agnostic and P either precedes E's function parameter scope or is in E's parameter-declaration-clause), the program is ill-formed. ...
[Example:
[=]<decltype(x) P>{}; // ok: P has type float [=]<decltype((x)) P>{}; // error: x refers to local entity but precedes the // lambda's function parameter scope = y){}; // error: x refers to local entity but is in the lambda's // parameter-declaration-clause
-- end example]
Suggested resolution (carves out an exception for decltype only):
Change in 7.5.5.2 [expr.prim.id.unqual] paragraph 3 as follows:
If the unqualified-id appears in a lambda-expression at program point P and the entity is a local entity (6.1 [basic.pre]) or a variable declared by an init-capture (7.5.6.3 [expr.prim.lambda.capture]), then let S be the compound-statement of the innermost enclosing lambda-expression of P. If naming the entity from outside of an unevaluated operand within S would refer to an entity captured by copy in some intervening lambda-expression, then let E be the innermost such lambda-expression, and:
- If the unqualified-id is the operand of a decltype (9.2.9.6 [dcl.type.decltype]) or P is in E's function parameter scope but not its_parameter-declaration-clause_, then the type of the expression is the type of a class member access expression (7.6.1.5 [expr.ref]) naming the non-static data member that would be declared for such a capture in the object parameter (9.3.4.6 [dcl.fct]) of the function call operator of E. [Note 3: If E is not declared mutable, the type of such an identifier will typically be const qualified. —_end note_]
- Otherwise (if the unqualified-id is not the operand of a decltype and P either precedes E's function parameter scope or is in E's parameter-declaration-clause), the program is ill-formed. ...
[Example:
[=]<decltype(x) P>{}; // ok: P has type float [=]<decltype((x)) P>{}; // error: x refers to local entity but precedes the // lambda's function parameter scope = y){}; // error: x refers to local entity but is in the lambda's // parameter-declaration-clause
-- end example]
Additional notes (April, 2022):
Forwarded to EWG withpaper issue 1227, by decision of the CWG chair.
See paper P2579R0 for a more detailed discussion of the issue.