Skip to content

CWG3156 [expr.prim.lambda.capture] Clarify if =delete functions allowed(indented) in unevaluated contexts #841

@ranaanoop

Description

@ranaanoop

Full name of submitter: Anoop S. Rana

Reference (section label): [expr.prim.lambda.capture]

Link to reflector thread: https://stackoverflow.com/q/79878786/12002570

Issue description: Consider the definition of var in the below program that shows implementation divergence. GCC and Clang rejectes it while MSVC accepts it. Live demo


struct C
{
  C(int) = delete;
  C(){};
};

decltype([b = C(3)](){ return 4;}()) var; //msvc OK but gcc and clang rejects

decltype([C(3)](){ return 4;}()) var2; //accepted by all compilers
int main()
{

} 

The program is well-formed as per current wording as explained in the linked thread and for completeness here again below:

The program is well-formed and msvc is right to accept it because the the operand of decltype is an unevaluated operand which means here we're in unevaluated context and so the lambda is never actually evaluated. This is explained in detail below.


First, from expr.prim.lambda.capture#10:

For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified.
The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise.
A member of an anonymous union shall not be captured by copy.

This means that an unnamed non-static data member of type C will be declared in the closure type. Now, we move onto the initialization of this unnamed data member. From expr.prim.lambda.capture#15:

When the lambda-expression is evaluated, the entities that are captured by copy are used to direct-initialize each corresponding non-static data member of the resulting closure object, and the non-static data members corresponding to the init-captures are initialized as indicated by the corresponding initializer (which may be copy- or direct-initialization).
(For array members, the array elements are direct-initialized in increasing subscript order.) These initializations are performed in the (unspecified) order in which the non-static data members are declared.

Note the emphasis on "evaluated" in the above quoted reference. Here, the operand [b = C(3)](){ return 4;}() of the decltype specifier is never evaluated as it appears as an unevaluated operand which means that [expr.prim.lambda.capture#15] quoted above is not applicable. This can be seen from dcl.type:

The operand of the decltype specifier is an unevaluated operand.

Finally, expr.context:

In some contexts, unevaluated operands appear ([expr.prim.req], [expr.typeid], [expr.sizeof], [expr.unary.noexcept], [dcl.type.decltype], [temp.pre], [temp.concept]).
An unevaluated operand is not evaluated.

This means that the program is well-formed.


Essentially, since overload resolution is part of initilaization which itself won't be done here because the "When the lambda-expression is evaluated" isn't satisfied. That is, no initialization of the non-static data member so no question of selection of a deleted function. This is my reading of the standard. But it may not be the intended behavior. Kindly, clarify if =delete function can be used(misused?) this way.

Note that there is also dcl.fct.def.delete#2 in the standard that is intened to make this ill-formed:

A construct that designates a deleted function implicitly or explicitly, other than to declare it or to appear as the operand of a reflect-expression ([expr.reflect]), is ill-formed..

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions