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


69

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

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

要么

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

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

尽管看起来像是一种常见模式并且是合理的做法,但这是否违反了ODR(一个定义规则)?第二个样本是否每次都违反ODR?或者仅在头文件中至少有一个构造函数时才违反?



每次构造另一个lambda(与前一个无关)。
Jarod42'1

您是否在询问声明是否存在ODR冲突,如果声明存在于多个头文件中,在多个cpp文件中使用声明是否违反了ODR,或者在头文件中的其他内联函数中使用声明是否违反了ODR? /无/以上所有?
Yakk-Adam Nevraumont

1
我已经调整了问题,因为否则需要对默认参数进行解释(使解释变得复杂)。
哥伦布

Answers:


41

这归结为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已包含适当的规则,以确保此类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,所引用的重载运算符,转换函数,构造函数,运算符新函数和运算符Delete函数的隐式调用均应引用相同的函数,或指向 D;[…]

这实际上意味着在实体的定义中调用的所有函数在所有翻译单元中都应相同-或已在其定义内定义,例如本地类及其成员。即,lambda本身的使用没有问题,但是将其明确传递给功能模板是有问题的,因为它们是在定义之外定义的。

在带有的示例中C,闭包类型在类中定义(其范围是最小的闭包类型)。如果闭包类型在两个TU中有所不同(标准可能无意中暗示闭包类型的唯一性),则构造函数实例化并调用function的构造函数模板的不同专长,这违反了上面引用中的(6.4)。


1
评论不作进一步讨论;此对话已转移至聊天
乔治·斯托克

3
@GeorgeStocker评论在哪里?
哥伦布

8
@GeorgeStocker如果无法移动,则应保持原样。请不要删除有价值的讨论;这是一个。
哥伦布

8
@GeorgeStocker如果这么简单,为什么没有自动流程,当列表过长时会像以前那样盲目删除所有评论?我们都是程序员,我们都知道实现起来是多么微不足道。在这种情况下,为什么网站需要主持人手动干预?也许是因为希望主持人在这样做时使用自己的判断,以避免丢失宝贵的内容?关于“将其放入您的答案”:在删除内容之前,您是否给了Columbo一个机会将内容移入答案?
bogdan

2
我非常生气,以至于有些评论无意中删除了这些评论。世上谁会做这样的火腿手?
ForeverLearning '16

8

更新

毕竟我同意@Columbo的答案,但想加上实际的5美分:)

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

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


您是否知道任何编译器带有其依赖的特定ODR级别的描述?
curiousguy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.