这归结为 lambda 的类型是否因翻译单元而异。如果是这样,它可能会影响模板参数推导,并可能导致调用不同的函数——这意味着定义一致。这将违反 ODR(见下文)。
然而,这不是故意的。事实上,这个问题已经被核心问题 765提到过,它专门命名了具有外部链接的内联函数 - 例如f
:
7.1.2 [dcl.fct.spec] 第 4 段指定出现在具有外部链接的内联函数主体中的局部静态变量和字符串文字必须是程序中每个翻译单元中的相同实体。然而,关于本地类型是否同样需要相同,并没有说什么。
尽管符合标准的程序总是可以通过使用 typeid 来确定这一点,但最近对 C++ 的更改(允许本地类型作为模板类型参数,lambda 表达式闭包类)使这个问题更加紧迫。
2009 年 7 月会议记录:
类型应相同。
现在,该决议将以下措辞纳入[dcl.fct.spec]/4:
在函数体中定义的extern inline
类型在每个翻译单元中都是相同的类型。
(注意:MSVC 尚未考虑上述措辞,尽管它可能会在下一个版本中出现)。
因此,此类函数体内的 Lambda 是安全的,因为闭包类型的定义确实在块范围内([expr.prim.lambda]/3)。
因此,对的多个定义f
曾经被很好地定义过。
这个解决方案当然不会涵盖所有场景,因为有更多种类的具有外部链接的实体可以使用 lambda,特别是函数模板 - 这应该由另一个核心问题来解决。
同时,Itanium 已经包含适当的规则来确保此类 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
这实际上意味着在实体定义中调用的任何函数在所有翻译单元中都应相同 -或者已在其定义中定义,如本地类及其成员。即使用 lambda 本身没有问题,但将其传递给函数模板显然是有问题的,因为这些是在定义之外定义的。
在您的示例中C
,闭包类型是在类中定义的(其范围是最小的封闭类)。如果闭包类型在两个 TU 中不同,标准可能无意中暗示了闭包类型的唯一性,则构造函数实例化并调用function
的构造函数模板的不同特化,违反上述引用中的 (6.4)。