问题描述
是否可以在头文件中写入以下内容:
Can the following be written in a header file:
inline void f () { std::function<void ()> func = [] {}; }
或
class C { std::function<void ()> func = [] {}; C () {} };
我猜在每个源文件中,lambda 的类型可能不同,因此 std::function
中包含的类型(target_type
的结果会有所不同).
I guess in each source file, the lambda's type may be different and therefore the contained type in std::function
(target_type
's results will differ).
这是否违反了 ODR(一个定义规则),尽管看起来是一种常见的模式并且是合理的做法?第二个示例是每次都违反 ODR 还是仅当头文件中至少有一个构造函数时?
Is this an ODR (One Definition Rule) violation, despite looking like a common pattern and a reasonable thing to do? Does the second sample violate the ODR every time or only if at least one constructor is in a header file?
推荐答案
这归结为 lambda 的类型是否因翻译单元而异.如果是这样,它可能会影响模板参数推导并可能导致调用不同的函数 - 在什么是一致的定义中.这将违反 ODR(见下文).
This boils down to whether or not a lambda's type differs across translation units. If it does, it may affect template argument deduction and potentially cause different functions to be called - in what are meant to be consistent definitions. That would violate the ODR (see below).
然而,这不是故意的.其实这个问题之前核心问题 765,其中专门命名了具有外部链接的内联函数 - 例如 f
:
However, that isn't intended. In fact, this problem has already been touched on a while ago by core issue 765, which specifically names inline functions with external linkage - such as f
:
7.1.2 [dcl.fct.spec] 第 4 段指定出现在内联函数体中的局部静态变量和字符串文字外部链接在每个翻译单元中必须是相同的实体在程序中.然而,没有说本地类型是否同样要求相同.
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.
尽管符合标准的程序总是可以通过使用来确定这一点typeid,最近对 C++ 的更改(允许本地类型作为模板类型参数,lambda 表达式闭包类)提出这个问题更紧迫.
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.
类型应该是相同的.
现在,该决议将以下措辞纳入[dcl.fct.规范]/4:
Now, the resolution incorporated the following wording into [dcl.fct.spec]/4:
在 extern inline
函数体内定义的类型与每个翻译单元中的类型相同.
A type defined within the body of an
extern inline
function is the same type in every translation unit.
(注意:MSVC 尚未考虑上述措辞,尽管 它可能在下一个版本中).
(NB: MSVC isn't regarding the above wording yet, although it might in the next release).
因此,此类函数体内的 Lambda 是安全的,因为闭包类型的定义确实在块范围内([expr.prim.lambda]/3).
因此,f
的多个定义一直被很好地定义.
Lambdas inside such functions' bodies are therefore safe, since the closure type's definition is indeed at block scope ([expr.prim.lambda]/3).
Hence multiple definitions of f
were ever well-defined.
这个决议当然没有涵盖所有场景,因为有更多种类的具有外部链接的实体可以使用 lambda,特别是函数模板 - 这应该由另一个核心问题涵盖.
同时,Itanium 已经包含适当的规则 以确保此类 lambda 的类型在更多情况下一致,因此 Clang 和 GCC 应该已经大部分按预期运行.
This resolution certainly doesn't cover all scenarios, as there are many more kinds of entities with external linkage that can make use of lambdas, function templates in particular - this should be covered by another core issue.
In the meantime, Itanium already contains appropriate rules to ensure that such lambdas' types coincide in more situations, hence Clang and GCC should already mostly behave as intended.
关于为什么不同的关闭类型违反 ODR 的标准如下.考虑[basic.def.odr]中的要点(6.2)和(6.4)]/6:
Standardese on why differing closure types are an ODR violation follows. Consider bullet points (6.2) and (6.4) in [basic.def.odr]/6:
[...] 的定义可能不止一个.给定在多个翻译单元中定义的名为 D
的实体,则 D
的每个定义应包括相同的令牌序列;和
There can be more than one definition of […]. Given such an entity named
D
defined in more than one translation unit, then each definition ofD
shall consist of the same sequence of tokens; and
(6.2) - 在每个D
的定义中,对应的名字,查了一下根据[basic.lookup],应指代内定义的实体D
的定义,或者指的是同一个实体,之后重载解析 ([over.match]) 和部分匹配后模板专业化 ([temp.over]), […];和
(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) - 在 D
的每个定义中,重载操作符被引用,隐式调用转换函数,构造函数,运算符新函数和运算符删除函数,应参考相同的函数,或在定义中定义的函数D
;[…]
(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
; […]
这实际上意味着实体定义中调用的任何函数在所有翻译单元中都应相同 - 或已在其定义中定义,如本地类及其成员.IE.使用 lambda 本身没有问题,但将其传递给函数模板显然是有问题的,因为它们是在定义之外定义的.
What this effectively means is that any functions called in the entity's definition shall be the same in all translation units - or have been defined inside its definition, like local classes and their members. I.e. usage of a lambda per se is not problematic, but passing it to function templates clearly is, since these are defined outside the definition.
在您使用 C
的示例中,闭包类型是在类中定义的(其范围是最小的封闭范围).如果两个 TU 中的闭包类型不同,标准可能无意中暗示了闭包类型的唯一性,则构造函数实例化并调用 function
的构造函数模板的不同特化,违反了 (6.4)以上报价.
In your example with C
, the closure type is defined within the class (whose scope is the smallest enclosing one). If the closure type differs in two TUs, which the standard may unintentionally imply with the uniqueness of a closure type, the constructor instantiates and calls different specializations of function
's constructor template, violating (6.4) in the above quote.
这篇关于在头文件中使用 lambda 会违反 ODR 吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!