未定义对类静态constexpr结构的引用,g ++与clang

时间:2018-11-21 07:46:51

标签: c++ c++11 one-definition-rule

这是我的代码,a.cp​​p

struct int2
{
    int x, y;
};
struct Foo{
    static constexpr int bar1 = 1;
    static constexpr int2 bar2 = {1, 2};
};
int foo1(){
    return Foo::bar1; // this is ok for both clang++ and g++
}
int2 foo2(){
    return Foo::bar2; // undefined reference to `Foo::bar2' in clang++
}
int main(){ std::cout << foo2().x << std::endl; return 0; }

使用clang进行编译,clang++ -std=c++11 a.cpp

/tmp/a-0dba90.o: In function `foo2()':
a.cpp:(.text+0x18): undefined reference to `Foo::bar2'
clang-7: error: linker command failed with exit code 1 (use -v to see 
invocation)

g++ -std=c++11 a.cpp不会发出错误。

我的问题是,

  1. 谁适合上述代码? lang还是g ++?
  2. 为什么在巴生语中bar1正确,而bar2错误?

编译器版本:g ++ 5.4.0和clang 7.0.0

更新:该问题被标记为another question的重复项,但事实并非如此。我知道我可以在类外显式添加定义,以使它传递给clang。这个问题是关于为什么g ++&clang之间存在所有差异的原因。

2 个答案:

答案 0 :(得分:3)

您似乎假设,如果一个编译器是正确的,则另一个编译器必定是错误的。程序包含错误(然后接受该错误的编译器是错误的)或没有错误(然后拒绝该错误的编译器是错误的)。反过来,这依赖于一个隐含的假设,即所讨论的错误(即,缺少使用ODR的实体的定义)是可诊断的错误。不幸的是,事实并非如此。该标准明确指出:

  

[basic.def.odr/10]每个程序应准确地包含在该程序中被废弃语句之外使用的每个非内联函数或变量的一个定义; 无需诊断

由于该标准的规定存在问题且令人讨厌,所以就在那里。您的程序由于缺少定义而具有未定义的行为,因此不需要执行即可对其进行诊断。因此,在任何优化级别上,两种编译器在技术上都是正确的。

在具有强制复制删除功能的C ++ 17中,程序不再包含对有问题的变量的任何ODR使用constexpr静态数据成员隐式内联,不需要单独的定义(感谢Oliv)。

答案 1 :(得分:1)

回答我自己的问题。

我对odr-use有一些模糊的理解。

  • 对于文字类型Foo :: bar1,它不被使用,所以很好。
  • 对于struct Foo :: bar2:在函数体内返回一个struct时,它将调用其副本构造函数,该副本构造函数引用Foo::bar2。因此Foo::bar2用途很广,其定义必须存在于程序中的某个位置,否则将导致链接错误。

但是为什么g ++不抱怨?我想这与编译器优化有关。

验证我的猜测:

  1. 复制省略

    添加-fno-elide-constructors,g++ -fno-elide-constructors -std=c++11 a.cpp

      

    /tmp/ccg1z4V9.o:在函数foo2()': a.cpp:(.text+0x27): undefined reference to Foo :: bar2'

    所以,是的,复制省略会影响到这一点。 但是g++ -O1仍然获得通过。

  2. 函数内联

    添加-fno-line,g++ -O1 -fno-elide-constructors -fno-inline -std=c++11 a.cpp

      

    /tmp/ccH8dguG.o:在函数foo2()': a.cpp:(.text+0x4f): undefined reference to Foo :: bar2'

结论是复制省略和内联函数都会影响其行为。 g ++和clang之间的区别在于,默认情况下,g ++启用了复制省略功能,而clang没有。