This is CWG 2300 and the behavior of the program can be explained using basic.def.odr#15.6 and basic.def.odr that implies the output 12
to be correct.
From basic.def.odr#15.6:
- For any definable item D with definitions in multiple translation units,
the program is ill-formed; a diagnostic is required only if the definable item is attached to a named module and a prior definition is reachable at the point where a later definition occurs.
Given such an item, for all definitions of D, or, if D is an unnamed enumeration, for all definitions of D that are reachable at any given program point, the following requirements shall be satisfied.
15.3. ...
15.4. ...
15.5. ...
15.6. In each such definition, except within the default arguments and default template arguments of D, corresponding lambda-expressions shall have the same closure type (see below).
Note the "exception" given above for default arguments of D
. More importantly, note that the exception don't apply to your example because in your example foo
is a non-inline non-template function which means it can only ever be defined in only translation unit only. Which in turn means that there is only one definition of the lambda itself and hence only one unique closure type exist in your example.
This can further be seen from basic.def.odr:
If D is a template and is defined in more than one translation unit, then the preceding requirements shall apply both to names from the template's enclosing scope used in the template definition, and also to dependent names at the point of instantiation ([temp.dep]). These requirements also apply to corresponding entities defined within each definition of D (including the closure types of lambda-expressions, but excluding entities defined within default arguments or default template arguments of either D or an entity not defined within D). For each such entity and for D itself, the behavior is as if there is a single entity with a single definition, including in the application of these requirements to other entities.
[Note 4: The entity is still declared in multiple translation units, and [basic.link] still applies to these declarations.
In particular, lambda-expressions ([expr.prim.lambda]) appearing in the type of D can result in the different declarations having distinct types, and lambda-expressions appearing in a default argument of D might still denote different types in different translation units.
— end note]
[Example 6:
inline void f(bool cond, void (*p)()) {
if (cond) f(false, []{});
}
inline void g(bool cond, void (*p)() = []{}) {
if (cond) g(false);
}
struct X {
void h(bool cond, void (*p)() = []{}) {
if (cond) h(false);
}
};
If the definition of g
appears in multiple translation units, the program is ill-formed (no diagnostic required) because each such definition uses a default argument that refers to a distinct lambda-expression closure type.
The definition of X can appear in multiple translation units of a valid program; the lambda-expressions defined within the default argument of X::h within the definition of X denote the same closure type in each translation unit.
— end example]
From here also we come to the same conclusion that in your example the definition of function foo
appears only in one translation unit. The given program is well-formed and there is only one closure-type in your given example(in a single TU) and so the output 12
is correct as per the current wording.