可以在头文件中使用lambda违反ODR吗?

时间:2016-01-11 09:19:35

标签: c++ c++11 lambda language-lawyer one-definition-rule

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

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

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

我猜在每个源文件中,lambda的类型可能不同,因此std::function中包含的类型(target_type&#39}的结果会有所不同。

这是一个ODR(One Definition Rule)违规,尽管看起来像一个共同的模式和合理的事情吗?第二个示例是每次都违反ODR还是仅在头文件中至少有一个构造函数?

2 个答案:

答案 0 :(得分:39)

这归结为lambda的类型是否因翻译单位而异。如果是这样,它可能会影响模板参数推断,并可能导致调用不同的函数 - 这意味着一致的定义。这将违反ODR(见下文)。

然而,这并非有意。实际上,core issue 765已经触及了这个问题,它专门命名了具有外部链接的内联函数 - 例如f

  

7.1.2 [dcl.fct.spec]第4段指定出现在内联函数体中的局部静态变量和字符串文字   外部链接必须是每个翻译单元中的相同实体   在该计划中。 然而,关于是否本地类型,没有任何说法   同样要求是相同的。

     

虽然符合规定的程序总是可以通过使用来确定   of type,最近对C ++的更改(允许本地类型作为模板   类型参数,lambda表达式闭包类)提出这个问题   更紧迫。

     

2009年7月会议记录:

     

这些类型应该是相同的。

现在,该决议将以下措辞纳入[dcl.fct.spec]/4

  

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

(注意:MSVC尚未考虑上述措辞,尽管it might in the next release)。

因此,闭包类型的定义确实在块范围内([expr.prim.lambda]/3)。 因此,f的多个定义得到了很好的定义。

这个解决方案当然不包括所有场景,因为有更多种类的具有外部链接的实体可以使用lambda,特别是功能模板 - 这应该被另一个核心问题所涵盖。 与此同时,Itanium已经包含appropriate rules以确保此类lambda的类型在更多情况下重合,因此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)。

答案 1 :(得分:8)

<强>已更新

毕竟我同意@Columbo的回答,但想加上实用的五分钱:)。

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

现在,当ODR违规被报告为错误时,很可能会在出现问题的编译器中修复它。 MSVC以及可能不遵循Itanium ABI的其他一些。请注意,符合Itanium ABI标准的编译器(例如gcc和clang)已经为头定义的lambdas生成了ODR正确的代码。