在头文件中使用lambda是否违反了ODR?

以下内容可以写在一个头文件中:

inline void f () { std::function<void ()> func = [] {}; } 

要么

 class C { std::function<void ()> func = [] {}; C () {} }; 

我猜在每个源文件中,lambda的types可能是不同的,因此在std::functiontarget_type的结果将不同)中包含的types。

这是否违反ODR( 一个定义规则 ),尽pipe看起来像一个共同的模式和合理的事情? 第二个示例是否每次都违反了ODR,或者只有至less一个构造函数在头文件中?

这归结为拉姆达的types是否跨翻译单位不同。 如果是这样,它可能会影响模板参数的推导,并可能导致调用不同的函数 – 这意味着一致的定义。 这将违反ODR(见下文)。

但是,这不是意图。 事实上,这个问题早已被核心问题765所触及,它专门命名带有外部链接的内联函数,比如f

7.1.2 [dcl.fct.spec]第4段规定,在具有外部链接的内联函数主体中出现的本地静态variables和string文本必须是程序中每个翻译单元中的相同实体。 然而,关于是否同样要求地方types也是如此。

尽pipe一致的程序总是可以通过使用typeid来确定,但最近对C ++的改变( 允许本地types作为模板types参数,lambdaexpression式闭包类 )使得这个问题更加紧迫。

2009年7月会议logging:

types意图是相同的。

现在,该决议将以下措辞合并为[dcl.fct.spec] / 4 :

extern inline函数体内定义的types在每个翻译单元中都是相同的types。

(注意:虽然MSVC 可能在下一个版本中 ,但MSVC并不涉及上述措辞)。

因此封闭types的定义确实在块范围内( [expr.prim.lambda] / 3 ),所以这样的函数体内的Lambdas是安全的。
因此, f多重定义是有明确定义的。

这个解决scheme当然不能涵盖所有情况,因为有更多种类的外部链接的实体可以使用lambdas,特别是函数模板 – 这应该由另一个核心问题来解决。
与此同时,Itanium已经包含了适当的规则来确保这种lambdatypes在更多的情况下是一致的,因此Clang和GCC应该已经基本上按预期行事了。


以下是为什么不同的封闭types是ODR违规的原因。 考虑[basic.def.odr] / 6中的项目符号点(6.2)和(6.4):

可以有多个[…]的定义。 给定一个名为D的实体定义在多个翻译单元中,那么D每个定义应由相同的记号序列组成; 和

(6.2) – 在D每个定义中, 根据[basic.lookup]查找的对应名称是指在D的定义中定义的实体,或者在超载parsing后指同一个实体。匹配])和部分模板特化([temp.over])匹配后,[…]; 和

(6.4) – 在D每一个定义中,被引用的重载操作符, 转换函数, 构造函数 ,操作符新函数和操作符删除函数的隐式调用都 应该指向相同的函数,或者指向定义在D ; […]

这实际上意味着实体定义中调用的任何函数在所有的翻译单元中都应该是相同的, 或者在其定义中被定义 ,如本地类和它们的成员。 也就是说,lambda本身的使用是没有问题的,但明确地将它传递给函数模板是因为它们是在定义之外定义的。

在你的例子C ,闭包types是在类中定义的(它的作用域是最小的封闭types)。 如果闭包types在两个TU中不同,标准可能无意中隐含了闭包types的唯一性,那么构造函数实例化并调用function的构造函数模板的不同特化,违反上述引用中的(6.4)。

更新

毕竟我赞同@Columbo的回答,但要添加实际的5分:)

虽然ODR违规听起来很危险,但在这种特殊情况下这不是一个严重的问题。 在不同TU中创build的lambda类除了它们的typeid之外是相同的。 所以,除非你必须处理一个头部定义的lambda的typeid(或者一个取决于lambda的types),否则你是安全的。

现在,当ODR违规被报告为一个错误时,很有可能会在有问题的编译器中解决这个问题,例如MSVC和其他一些不遵循Itanium ABI的问题。 请注意,Itanium ABI一致性编译器(例如gcc和clang)已经为头文件定义的lambdas生成了ODR正确的代码。