【问题标题】:Can using a lambda in header files violate the ODR?在头文件中使用 lambda 会违反 ODR 吗?
【发布时间】:2016-04-15 13:01:45
【问题描述】:

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

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

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

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

这是否违反了 ODR (One Definition Rule),尽管这看起来是一种常见的模式和合理的做法?第二个样本是每次都违反 ODR 还是仅在头文件中至少有一个构造函数时才违反 ODR?

【问题讨论】:

  • 每次构造另一个 lambda(与前一个无关)。
  • 如果声明存在于多个头文件中是否违反 ODR,如果在多个 cpp 文件中使用它是否违反 ODR,或者是否在 进一步 i> 头文件中的内联函数会违反 ODR,还是上述部分/无/全部?
  • 我已经调整了问题,否则需要对默认参数进行解释(使解释复杂化)。

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


【解决方案1】:

这归结为 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)。

【讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • @GeorgeStocker cmets 在哪里?
  • @GeorgeStocker 如果它不能被移动,你应该让它保持原样。请不要删除有价值的讨论;这是一个。
  • @GeorgeStocker 如果就这么简单,为什么没有像您那样在列表过长时盲目删除所有 cmets 的自动化过程?我们都是程序员,我们都知道实现它是多么微不足道。在这种情况下,为什么网站需要版主手动干预?也许是因为版主在这样做时应该运用他们的判断,正是为了避免丢失有价值的内容?关于“把它放在你的答案中”:你有没有给 Columbo 机会在删除之前将内容移到答案中?
  • 我很生气,因为 cmets 被删除了,因为他们只是碰巧参加了这次讨论。世上谁会做这种笨手笨脚的事?
【解决方案2】:

更新

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

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

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

【讨论】:

  • 你知道任何编译器都有它所依赖的特定 ODR 级别的描述吗?
猜你喜欢
  • 2011-11-29
  • 2020-04-03
  • 1970-01-01
  • 1970-01-01
  • 2021-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多