头文件和 odr 中的 constexpr 全局常量

constexpr global constants in a header file and odr

不幸的是,我对 constexpr、头文件中声明的全局常量和 odr 感到有些困惑。

简而言之:我们可以从这里得出结论吗

https://isocpp.org/files/papers/n4147.pdf

那个

constexpr MyClass const MyClassObj () { return MyClass {}; }
constexpr char const * Hello () { return "Hello"; }

优于

constexpr MyClass const kMyClassObj = MyClass {};
constexpr char const * kHello = "Hello";

用于在头文件中定义全局变量 如果我想要 "just use" 那些 globally declared/defined 实体并且不想考虑 如何 我使用它们?

注意:从 C++17 开始,您可以声明 variables as inline


TL;DR:如果您想(非常)安全,请使用 constexpr 函数。不过,这并不是本质上必需的,如果您对这些 object 执行微不足道的操作并且只对它们的价值感兴趣,或者根本不在下面列出的危险场景中使用它们,那么肯定不会.

根本问题是 const 命名空间范围内的变量(通常)具有内部链接 ([basic.link]/(3.2))。这意味着 每个编译相应 header 的翻译单元将观察到不同的实体(即符号)。

现在假设我们在 header 中使用那些 object 有一个模板或内联函数。 ODR 对这种情况非常准确 - [basic.def.odr]/6:

"initialized with a constant expression"肯定满足了,既然我们说的是constexpr"the object has the same value in all definitions of D" 如果你不胡闹也是如此。

"the object isn't odr-used" 可能是唯一有问题的条件。基本上,它要求您不必将变量运行时存在作为符号,这反过来意味着

  • 您没有将它绑定到引用(=> 您没有转发它!)

  • 你不(既不明确也不隐含地)获取它的地址。

第二条规则的唯一例外是数组,它可以在下标操作中隐式地取地址,只要不违反以上两条规则产生的泛左值。

更准确地说,odr-use 受制于 [basic.def.odr]/3:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5).

将 l-t-r 应用于任何 constexpr 变量将按照第一部分的要求运行。第二部分要求将变量用作 value 而不是实际的 object;也就是说,它最终要么被丢弃,要么被直接评估,给出上述经验法则。

如果在内联函数、模板等内部避免使用 odr-use 变量,就可以了。但是如果你使用相应的 constexpr 函数的 return 值,你就不必担心,因为纯右值已经表现得更像 values/literals(而不是 objects)并且 constexpr 函数是内联并且绝对不会违反 ODR(如果你不在其中使用 constexpr 变量!)。