可以在头文件中写入以下内容:
inline void f () { std::functionfunc = [] {}; }
要么
class C { std::functionfunc = [] {}; C () {} };
我想在每个源文件中,lambda的类型可能不同,因此包含的类型std::function
(target_type
的结果会有所不同).
这是一个ODR(一个定义规则)违规,尽管看起来像一个共同的模式和合理的事情?第二个示例是每次都违反ODR还是仅在头文件中至少有一个构造函数?
这归结为lambda的类型是否因翻译单位而异.如果是这样,它可能会影响模板参数推断,并可能导致调用不同的函数 - 这意味着一致的定义.这将违反ODR(见下文).
但是,这不是预期的.实际上,这个问题已经被核心问题765触及了,它特别命名了具有外部链接的内联函数 - 例如f
:
7.1.2 [dcl.fct.spec]第4段规定出现在具有外部链接的内联函数体中的局部静态变量和字符串文字必须是程序中每个翻译单元中的相同实体.然而,没有任何关于本地类型是否也需要相同的说法.
虽然一致的程序总是可以通过使用typeid来确定这一点,但最近对C++的更改(允许本地类型作为模板类型参数,lambda表达式闭包类)使这个问题更加紧迫.
2009年7月会议记录:
类型旨在相同.
现在,该决议将以下措辞纳入[dcl.fct.spec]/4:
extern inline
函数体内定义的类型在每个翻译单元中都是相同的类型.
(注意:MSVC尚未考虑上述措辞,尽管可能在下一版本中).
因此,这些函数体内的Lambdas是安全的,因为闭包类型的定义确实在块范围内([expr.prim.lambda]/3).
因此,多种定义f
都是明确定义的.
这个解决方案当然不包括所有场景,因为有更多种类的具有外部链接的实体可以使用lambda,特别是功能模板 - 这应该被另一个核心问题所涵盖.
与此同时,Itanium已经包含适当的规则来确保这些lambda类型在更多情况下重合,因此Clang和GCC应该已经大部分按预期运行.
关于为什么不同的闭包类型是ODR违规的标准如下.考虑[basic.def.odr]/6中的要点(6.2)和(6.4):
[...]可以有多个定义.鉴于这样一个名称
D
在多个翻译单元中定义的实体,则每个定义D
应由相同的令牌序列组成; 和(6.2) - 在[basic.lookup]中查找的相应名称的每个定义中
D
,应指过定义中定义的D
实体,或者在重载解析后引用同一实体([over.match]) )和部分模板专业化([temp.over])匹配后,[...]; 和(6.4) - 在每个定义中
D
,所引用的重载运算符,对转换函数,构造函数,运算符新函数和运算符删除函数的隐式调用,应引用相同的函数,或者定义中定义的函数D
; [...]
这实际上意味着实体定义中调用的任何函数在所有翻译单元中应该是相同的 - 或者已经在其定义中定义,如本地类及其成员.即使用lambda本身并不成问题,但将其传递给函数模板显然是因为这些是在定义之外定义的.
在您的示例中C
,闭包类型在类中定义(其范围是最小的封闭类).如果闭包类型在两个TU中不同,标准可能无意中暗示了闭包类型的唯一性,则构造函数实例化并调用function
构造函数模板的不同特化,违反上述引用中的(6.4).
更新
毕竟我同意@Columbo的答案,但想加实用五分钱:)
虽然ODR违规听起来很危险,但在这种特殊情况下并不是一个严重的问题.在不同的TU中创建的lambda类除了它们的typeid之外是等价的.因此,除非你必须处理头定义的lambda(或一个取决于lambda的类型)的typeid,否则你是安全的.
现在,当ODR违规被报告为错误时,很有可能在具有问题的编译器中修复它,例如MSVC以及可能不遵循Itanium ABI的其他一些问题.请注意,符合Itanium ABI标准的编译器(例如gcc和clang)已经为头定义的lambda生成了ODR校正代码.