这归结为 lambda 的类型是否因翻译单元而异。如果是这样,它可能会影响模板参数推导,并可能导致调用不同的函数——这意味着定义一致。这将违反 ODR(见下文)。
但是,这不是故意的。其实这个问题之前core issue 765已经提到过,专门给内联函数命名为外部链接——比如f:
7.1.2 [dcl.fct.spec] 第 4 段指定出现在内联函数体中的局部静态变量和字符串文字
外部链接必须是每个翻译单元中的相同实体
在节目中。 但是,关于本地类型是否
同样要求相同。
虽然一个符合标准的程序总是可以通过使用来确定这一点
typeid,最近对 C++ 的更改(允许本地类型作为模板
类型参数,lambda 表达式闭包类)提出这个问题
更紧迫。
2009 年 7 月会议记录:
类型应相同。
现在,该决议将以下措辞纳入[dcl.fct.spec]/4:
在extern inline 函数体中定义的类型在每个翻译单元中都是相同的类型。
(注意:尽管it might in the next release,MSVC 尚未考虑上述措辞)。
因此,此类函数体内的 Lambda 是安全的,因为闭包类型的定义确实在块范围内 ([expr.prim.lambda]/3)。
因此,f 的多个定义都得到了很好的定义。
这个解决方案当然不会涵盖所有场景,因为有更多种类的具有外部链接的实体可以使用 lambda,尤其是函数模板 - 这应该由另一个核心问题来解决。
同时,Itanium 已经包含 appropriate rules 以确保此类 lambdas 的类型在更多情况下一致,因此 Clang 和 GCC 应该已经按预期运行。
关于为什么不同的闭包类型违反 ODR 的标准如下。考虑[basic.def.odr]/6 中的要点(6.2)和(6.4):
[…] 可以有多个定义。给定这样一个名为D 的实体在多个翻译单元中定义,那么D 的每个定义应包含
相同的令牌序列;和
(6.2) - 在D的每个定义中,对应的名字,查找
根据 [basic.lookup],应指在
D的定义,或应指同一实体,后
重载决议([over.match])和部分匹配后
模板专业化([temp.over]),[…];和
(6.4) - 在D 的每个定义中,引用的重载运算符,
隐式调用转换函数、构造函数、
算子新增功能和算子删除功能,参考
相同的函数,或定义在定义中的函数
D; […]
这实际上意味着在实体定义中调用的任何函数在所有翻译单元中都应相同 - 或已在其定义中定义,如本地类及其成员。 IE。使用 lambda 本身没有问题,但将其传递给函数模板显然是有问题的,因为这些是在定义之外定义的。
在您使用C 的示例中,闭包类型是在类中定义的(其范围是最小的封闭类)。如果两个 TU 中的闭包类型不同,标准可能无意中暗示了闭包类型的唯一性,则构造函数实例化并调用 function 的构造函数模板的不同特化,违反了上述引用中的 (6.4)。