Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When are lambda function call operators instantiated?

Suppose a non-generic lambda appears within a templated entity, as in the code below:

template <class T, auto F = [](T x) { *x; }>
void foo(T t) {}

void foo(...) {}

int main() {
    foo(42);
}

[expr.prim.lambda.closure]/2 is relevant here:

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [...]

Therefore, during substitution into foo<int>, a lambda closure type is declared in global scope, and its function call operator is not a template. That lambda closure type is a templated entity ([temp.pre]/8.5), and therefore so is its function call operator ([temp.pre]/8.3). But the latter is neither a function template specialization, nor a member of a class template specialization, for which implicit instantiation would occur as described in [temp.inst]/5.

The most straightforward reading is that the lambda function call operator in the above code is not subject to as-needed instantiation; it is simply defined at the same time as the closure type. This appears to be consistent with GCC rejecting the above code. Another possible interpretation is that it is intended to be instantiated as needed like a function template specialization and there is an oversight in the wording; this would make the above code well-formed, with foo<int> being called. Clang seems to disagree with both interpretations; it calls foo(...), as if the other overload were subject to a substitution failure in the immediate context (which seems wrong to me; lambda expressions are not in the immediate context).

like image 322
Brian Bi Avatar asked May 03 '26 18:05

Brian Bi


1 Answers

After mulling this over, I believe GCC is correct. The meaning of instantiation of a templated entity should, by default, be considered to include the instantiation of any entity it encloses, other than one that is still dependent after the nearest enclosing entity is instantiated. For example, in

template <class T>
T foo(T t) {
    return t + 1;
}

we don't need an explicit rule in the standard that says that when foo is instantiated, the return statement in its body is also instantiated. That's implicit.

That's also why the standard merely has a note stating that a local class, and the members of a local class, are not subject to separate instantiation from the function definition whose scope they belong to. Notes are non-normative, so a note such as this merely serves to point out a consequence of the normative rules. Because a local class's definition, and the definition of each of its members, occurs lexically within the function definition, it is already implicit that all such enclosed definitions are instantiated when the function definition is.

There are various exceptions enumerated in [temp.inst] to the general rule; the default arguments of a function are not instantiated as part of the instantiation of the function declaration or definition in which they lexically occur, for example ([temp.inst]/5). Because there is no such exception for a lambda-expression (or the members of a lambda-expression) that is lexically within an enclosing templated entity, the lambda-expression and the definition of its closure type and the closure type's operator() must be instantiated as part of the instantiation of the nearest enclosing templated entity, at which point the lambda-expression and its closure type are no longer dependent. If it is a generic lambda, its operator() is a function template and its instantiation is deferred until required by [temp.inst]/5; if it is a non-generic lambda, then it is no longer dependent and is instantiated immediately.

like image 193
Brian Bi Avatar answered May 05 '26 08:05

Brian Bi