是否使用了静态constexpr变量odr?

时间:2018-09-10 05:53:51

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

给出以下代码,Foo::FOO1是否已使用ODR?

#include <iostream>
#include <map>
#include <string>

class Foo
{
public:
    static constexpr auto FOO1 = "foo1";
    void bar();
};

void Foo::bar()
{
    const std::map<std::string, int> m = {
        {FOO1, 1},
    };
    for (auto i : m)
    {
        std::cout << i.first << " " << i.second << std::endl;
    }
}

int main()
{
    Foo f;
    f.bar();
    return 0;
}

使用-O1或更高版本编译代码是可以的,但是如果使用-O0进行编译,则会出现以下错误(请参见coliru example

undefined reference to `Foo::FOO1'

表示它已使用ODR。是哪一个?


我知道上面的代码可以用-O很好地构建,但是在a real (and more complex) case中:

  • 代码可以通过-O2进行编译和链接
  • 代码获得了以上undefined reference错误LinkTimeOptimization(-O2 -flto

是否表明优化(-O)和LinkTimeOptimization(-flto)都会影响ODR使用规则?在C ++ 14和C ++ 17之间会发生变化吗?

1 个答案:

答案 0 :(得分:6)

规则为[basic.def.odr]/4

  

通过x 除非应用,变量<{1}}的名称ex可能被评估为表达式ex使用从左值到右值到x的转换将生成一个常量表达式,该常量表达式不会调用任何非平凡函数,并且如果x是对象,则ex是以下项的元素表达式e的潜在结果,其中将左值到右值转换([conv.lval])应用于e,或者e是一个废弃值表达式([ expr.prop])。

显然第一部分是满意的(FOO1constexpr,所以从左值到右值的转换确实会产生一个常量表达式而无需调用非平凡的函数),但是第二部分是吗?

我们正在构建map。相关的constructor thereinitializer_list<value_type>,即initializer_list<pair<const string, int>>pair有一个bunch of constructors,但此处要调用的是:

template <class U1, class U2>
constexpr pair(U1&& x, U2&& y); // with U1 = char const*&, U2 = int

这里的重要部分是我们不是直接构造string,而是通过pair的转换构造函数,该构造函数涉及绑定对FOO1的引用。这是一个奇怪的用途。这里没有左值到右值的转换,这也不是一个丢弃值的表达式。

基本上,当您使用某物的地址时,这是一种奇怪的用法-它必须具有定义。因此,您必须添加一个定义:

constexpr char const* Foo::FOO1;

请注意,另一方面:

std::string s = FOO1;

不会会成为odr用途。在这里,我们直接调用带有char const*参数的构造函数,该参数将是从左值到右值的转换。


在C ++ 17中,我们在[dcl.constexpr]中得到了这个新句子:

  

用constexpr说明符声明的函数或静态数据成员隐式是内联函数或变量([dcl.inline])。

这不会更改odr的使用,FOO1仍在程序中使用。但这确实使FOO1隐式成为一个内联变量,因此您不必显式为其添加定义。太酷了。


还请注意,仅仅因为程序进行了编译和链接并不意味着未定义的变量未被使用。

  

那么这表明优化(-O)和LinkTimeOptimization(-flto)都会影响ODR使用规则吗?

他们没有。这样的优化很酷。