【问题标题】:Is static constexpr variable odr-used?使用静态 constexpr 变量 odr 吗?
【发布时间】:2018-09-10 05:53:51
【问题描述】:

给出下面的代码,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 之间会发生变化吗?

【问题讨论】:

  • 用 c++17 编译它对我有用。 g++ -std=c++17 -O0 -Wall -pedantic -pthread main.cpp && ./a.out
  • 嗯...是的,我应该指定我现在使用的是 c++14。
  • 对象声明或非静态成员函数(C++14 前)中使用的 constexpr 说明符暗示 const。在函数或静态成员变量 (C++17 起) 声明中使用的 constexpr 说明符暗示内联。 en.cppreference.com/w/cpp/language/constexpr
  • 在 C++17 之前它不被认为是定义的,它只是在那里声明的。推导类型使这变得复杂。相关:stackoverflow.com/questions/38043442/…
  • 这里的措辞让我很困惑。 “使用 ODR 的正确方法是什么 [...]?”作为一个问题对我来说没有意义(没有单一的正确方式来使用某些东西,这是一个奇怪的目标) - 看起来实际的问题只是它是否 is odr 使用与否。我继续编辑了这个 - 如果你不同意,请告诉我。

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


【解决方案1】:

规则是[basic.def.odr]/4:

变量x 的名称显示为潜在评估表达式exexodr-used 除非应用左值到右值转换为x 会生成一个不调用任何重要函数的常量表达式,如果x 是一个对象,ex 是表达式e 的潜在结果集的一个元素,其中任一左值到右值的转换 ([conv.lval]) 应用于 e,或者 e 是丢弃值表达式 ([expr.prop])。

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

我们正在构建一个map。相关的constructor there 采用initializer_list&lt;value_type&gt;,即initializer_list&lt;pair&lt;const string, int&gt;&gt;pair 有一个 bunch of constructors,但这里会调用:

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

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

基本上,当您获取某物的地址时,这是一种 odr-use - 它必须有一个定义。所以你必须添加一个定义:

constexpr char const* Foo::FOO1;

请注意,另一方面,这是:

std::string s = FOO1;

不会是一种气味用途。在这里,我们直接调用带有char const* 参数的构造函数,这将是左值到右值的转换。


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

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

这不会改变关于 odr-use 的任何内容,FOO1 在您的程序中仍然是 odr-used。但它确实使FOO1 隐式成为内联变量,因此您不必显式地为其添加定义。很酷。


还要注意,仅仅因为程序编译和链接并不意味着缺少定义的变量没有被 ODR 使用。

也就是说优化(-O)和LinkTimeOptimization(-flto)都会影响ODR的使用规则?

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

【讨论】:

  • 那么使用-std=c++14-O,代码编译得很好,因为它通过内联变量优化了代码?
猜你喜欢
  • 2016-07-30
  • 2020-10-08
  • 1970-01-01
  • 2017-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-06
  • 1970-01-01
相关资源
最近更新 更多