我在使用GCC时收到一个我不理解的子对象链接警告。该警告可以用以下代码演示。
示例h:
#ifndef EXAMPLE_H
#define EXAMPLE_H
static constexpr int value{1};
template <auto& N> struct Base {};
struct Foo : Base<value> {};
#endif
字符串
example.cpp:
#include "example.h"
型
编译此代码会从GCC中产生以下输出:
/app/example.h:7:8: error: 'Foo' has a base 'Base<value>' which has internal linkage [-Werror=subobject-linkage]
7 | struct Foo : Base<value> {};
| ^~~
型
Godbolt链接:https://godbolt.org/z/M5eqz9qK6
如果我把example.h的内容直接放到example.cpp中,那么它编译得很好。Clang和msvc不会生成同样的警告。这段代码有问题吗?或者这是GCC中的一个bug?
1条答案
按热度按时间p8ekf7hl1#
这只是一个警告,而不是一个错误。它是格式良好的C++,如果你不使用
-Werror
,它也可以编译。但警告是合理的。
您正在将
Foo
的定义放在一个头文件中,这通常意味着它打算包含在多个翻译单元中。value
具有内部存储持续时间,因为它是一个非模板、非内联、非extern
常量限定的变量。(static
是冗余的。)因此,在每个翻译单元中,
value
是不同的对象。由于Base
将引用作为模板参数,因此Base<value>
是Base
在不同翻译单元中的不同特化,因为模板参数在每个翻译单元中引用不同的对象。然后,如果你在两个不同的翻译单元中包含header,程序将有未定义的行为,因为
Foo
的定义违反了单定义规则,粗略地说,编译器无法决定Foo
是否应该将Base<value1>
或Base<value2>
作为基类(其中value1
和value2
是不同翻译单位中value
的两个示例的发明名称)。更准确地说,单定义规则不仅要求同一个类的两个定义由完全相同的标记序列构成,而且要求在定义中查找的所有名称都引用每个定义中的同一个实体(有些例外在这里不适用)。事实上,
value
被用作引用模板参数是不相关的。查找中的差异,再加上value
是odr使用的,就足以违反ODR。(有关精确规则,请参见[basic.def.odr]/13.9。)因此,警告告诉你,你的头永远不能包含在一个以上的翻译单元中,而不会导致未定义的行为,这可能不是故意的。