C++ gcc和clang中constexpr静态成员变量的链接器错误
我有一个片段:C++ gcc和clang中constexpr静态成员变量的链接器错误,c++,c++11,c++14,C++,C++11,C++14,我有一个片段: enum class EC {a, b}; struct B { constexpr B(EC ec): ec_(ec) {} EC ec_; }; struct A_base { constexpr A_base(B b): b_(b) { } B b_; }; struct A: A_base { static constexpr B bbb = EC::a; constexpr A(B bbbb): A_base(bbb
enum class EC {a, b};
struct B {
constexpr B(EC ec): ec_(ec) {}
EC ec_;
};
struct A_base {
constexpr A_base(B b): b_(b) { }
B b_;
};
struct A: A_base {
static constexpr B bbb = EC::a;
constexpr A(B bbbb): A_base(bbbb) { }
};
int main()
{
A a1(A::bbb); // 1
A a2{A::bbb}; // 2
A a3 = A::bbb; // 3
A a4 = {A::bbb}; // 4
}
它由支持c++17的现代编译器编译而成。使用c++11和c++14标准支持链接器时发生错误。这个问题已经在和其他一些讨论中讨论过了。我理解为什么会发生这种错误
但有些事情我不明白:
A
s的所有初始化都调用A::A(B)
构造函数,它需要复制AB
,该构造函数使用编译器生成的B::B(B const&)
。将变量绑定到引用(a::bbb
)是该变量的odr用法。[basic.def.odr]中的具体规则是:
除非应用
从左值到右值的转换(4.1)到x生成一个不调用任何非平凡函数的常量表达式(5.20)
函数,如果x是对象,则ex是表达式e的潜在结果集的元素,其中
左值到右值的转换(4.1)应用于e,或者e是一个废弃的值表达式(第5条)
对复制构造函数的第一个调用将涉及将A::bbb
绑定到引用,该引用既不是左值到右值的转换,也不是丢弃的值表达式,因此使用odr
最重要的规则是:
每个程序应包含每个非内联函数或变量的一个定义,该函数或变量是在该程序中使用的odr,而不是丢弃的语句(6.4.1);无需诊断
A::bbb
使用odr,但缺少定义,因此我们违反了该规则-通常称为odr违反。但由于编译器在这种情况下不需要发出诊断(“无需诊断”,简称NDR),因此程序是未定义的行为。对于程序员来说,这类问题有时是令人沮丧的,但对于编译器来说,诊断这些问题是非常困难的,所以这是我们不得不面对的问题
很可能在更高的优化级别上,编译器只是省略了副本,因此不需要为a::bbb
调用B::B(B const&)
。。。至于为什么不同的初始化处理方式不同?可能是由于不同的优化过程。最终,它不会改变这是一个odr冲突的事实——不管代码是否编译和链接
在C++17之后
因此,
static constexpr
数据成员是隐式内联的,这意味着现在a::bbb
确实有了定义,并且现在没有odr冲突。C++17很酷 是的,这很正常。这是未定义的行为。编译器可以做任何事情。“编译器与关闭的优化不同是正常的吗”--是的。如果在更高的优化中,编译器/链接器能够消除对某个符号的所有引用,则可以不定义该符号。(尽管如此,为了确保程序的格式良好,您还是应该定义它。)这真的是一种未定义的行为吗?请您提供一个链接,将这种情况描述为未定义的行为,好吗?@SeleznevAnton请参见C++14的[basic.def.odr]“每个程序都应包含该程序中odr使用的每个非内联函数或变量的一个定义;无需诊断”。上一段定义了使用的odr,其中包括代码中的用法。“无需诊断”是指未定义的行为([intro.compliance]/2.3)@Barry同意,但我赞成Sam的解释,反对你的非建设性行为!