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

Can using a lambda in header files violate the ODR?

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

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

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

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

这是否违反 ODR (One Definition Rule),尽管看起来很常见并且是合理的做法?第二个示例是每次都违反 ODR 还是仅当头文件中至少有一个构造函数时才违反?

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

然而,这不是故意的。其实这个问题早就被core issue 765提到过,它专门命名了带有外部链接的内联函数——比如f

7.1.2 [dcl.fct.spec] paragraph 4 specifies that local static variables and string literals appearing in the body of an inline function with external linkage must be the same entities in every translation unit in the program. Nothing is said, however, about whether local types are likewise required to be the same.

Although a conforming program could always have determined this by use of typeid, recent changes to C++ (allowing local types as template type arguments, lambda expression closure classes) make this question more pressing.

Notes from the July, 2009 meeting:

The types are intended to be the same.

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

A type defined within the body of an extern inline function is the same type in every translation unit.

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

因此,此类函数体内的 Lambda 是安全的,因为闭包类型的定义确实在块范围内 ([expr.prim.lambda]/3)。
因此 f 的多个定义曾经是 well-defined。

这个决议当然没有涵盖所有场景,因为有更多种类的实体可以使用 lambda 的外部链接,特别是函数模板 - 这应该由另一个核心问题涵盖。
与此同时,Itanium 已经包含 appropriate rules 以确保此类 lambda 的类型在更多情况下一致,因此 Clang 和 GCC 应该已经基本按预期运行。


关于为什么不同的闭包类型是 ODR 违规的标准语如下。考虑 [basic.def.odr]/6:

中的要点 (6.2) 和 (6.4)

There can be more than one definition of […]. Given such an entity named D defined in more than one translation unit, then each definition of D shall consist of the same sequence of tokens; and

(6.2) - in each definition of D, corresponding names, looked up according to [basic.lookup], shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution ([over.match]) and after matching of partial template specialization ([temp.over]), […]; and

(6.4) - in each definition of D, the overloaded operators referred to, the implicit calls to conversion functions, constructors, operator new functions and operator delete functions, shall refer to the same function, or to a function defined within the definition of D; […]

这实际上意味着实体定义中调用的任何函数在所有翻译单元中都应相同 - 或已在其定义中定义,如本地 [=54] =]es 和他们的成员。 IE。使用 lambda 本身没有问题,但将它传递给函数模板显然是有问题的,因为它们是在定义之外定义的。

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

已更新

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

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

现在,当 ODR 违规被报告为错误时,很有可能会在有问题的编译器中修复它,例如MSVC,可能还有其他一些不遵循 Itanium ABI 的。请注意,Itanium ABI 兼容编译器(例如 gcc 和 clang)已经为 header-defined lambda 生成 ODR-correct 代码。