8

这可能是一个不寻常的问题,因为它要求对另一个问题的简短回答以及与之相关的 C++11 标准的某些方面进行更全面的解释。

为了便于参考,我将在这里总结参考的问题。OP 定义了一个类:

struct Account 
{
    static constexpr int period = 30;
    void foo(const int &) { }
    void bar() { foo(period); } //no error?
};

并且想知道为什么他对类内初始化静态数据成员的使用没有错误(一本书提到这是非法的)。Johannes Schaub 的回答指出:

  1. 这违反了单一定义规则
  2. 不需要诊断。

尽管我依赖这个答案的来源和有效性,但老实说我不喜欢它,因为我个人觉得它太神秘了,所以我试图自己想出一个更有意义的答案,但只取得了部分成功。相关似乎是§ 9.4.2/4:

“在程序中使用 odr (3.2)的静态数据成员应该有一个确切的定义;不需要诊断 [重点是我的]

这让我更接近一点。这就是第 3.2/2 节定义odr-used变量的方式:

“名称显示为潜在求值表达式的变量是 odr-used除非它是满足出现在常量表达式 (5.19) 中的要求并且立即应用左值到右值转换 (4.1) 的对象” [重点是我的]

在OP的问题中,变量period显然满足出现在常量表达式中的要求,是一个constexpr变量。所以肯定要在第二个条件中找到原因:并且立即应用左值到右值的转换(4.1)

这是我在解释标准时遇到困难的地方。这第二个条件实际上意味着什么?它涵盖哪些情况?这是否意味着如果静态constexpr变量是从函数返回的,则它不会被ODR 使用(因此可以在类内初始化)?

更一般地说:你可以对静态constexpr变量做什么,以便你可以在课堂上初始化它?

4

2 回答 2

3

这是否意味着如果从函数返回静态 constexpr 变量,则不使用 odr(因此可以在类内初始化)?

是的。

本质上,只要您将其视为value,而不是object,那么它就不是 odr-used 的。考虑一下,如果您粘贴了该值,则代码将具有相同的功能-这是将其视为右值的时候。但是在某些情况下它不会。

只有少数情况下不对基元执行左值到右值转换,这就是引用绑定,&obj可能还有其他一些情况,但很少。请记住,如果编译器给你一个const int&引用period,那么你必须能够获取它的地址,此外,这个地址对于每个 TU必须是相同的。这意味着,在 C++ 可怕的 TU 系统中,必须有一个明确的定义。

如果它不是 odr-used,编译器可以在每个 TU 中制作一个副本,或者替换该值,或者任何它想要的,并且您无法观察到差异。

于 2013-01-27T14:10:52.747 回答
3

你错过了前提的一部分。Account::period如果您还在某处定义(但没有提供初始化程序),则上面给出的类定义是完全有效的。见 9.4.2/3 的最后一句话:

如果该成员在程序中被 odr-used (3.2) 并且命名空间范围定义不应包含初始化程序,则该成员仍应在命名空间范围内定义。

整个讨论仅适用于静态 constexpr 数据成员,不适用于命名空间范围的静态变量。当静态数据成员constexpr(而不是简单的 const)时,您必须在类定义中初始化它们。所以你的问题应该是:你可以用静态 constexpr 数据成员做什么,这样你就不必在命名空间范围内为它提供定义?

从前面看,答案是成员不能被odr-used而且您已经找到了odr-used定义的相关部分。因此,您可以在没有潜在评估的上下文中使用该成员- 作为未评估的操作数(例如sizeofor decltype)或其子表达式。您可以在立即应用左值到右值转换的表达式中使用它。

所以现在我们要讨论变量的什么用途导致立即左值到右值的转换?

部分答案在 §5/8 中:

每当一个泛左值表达式作为操作数的操作数出现时,该操作数需要一个纯右值,左值到右值 (4.1)、数组到指针 (4.2) 或函数到指针 (4.3) 标准转换是应用于将表达式转换为纯右值。

对于基本上适用于所有应用标准算术转换的运算符的算术类型。因此,您可以在各种算术和逻辑运算中使用该成员,而无需定义。

我不能列举所有事情,你可以或不能在这里做,因为某事物是 [g]lvalue 或 [p]rvalue 的要求遍布整个标准。经验法则是:如果只使用变量的,则应用左值到右值的转换;如果变量用作对象,则将其用作左值

您不能在明确需要左值的上下文中使用它,例如作为地址运算符或变异运算符的参数)。左值引用的直接绑定(没有转换)就是这样一个上下文。

更多示例(没有详细的标准分析):

您可以将它传递给函数,除非函数参数是可以直接绑定变量的引用。

For returning it from a function: if implicit conversion to the return type involves a user-defined conversion function, the rules for passing to a function apply. Otherwise you can return it, unless the function returns an lvalue (a reference) referring directly to the variable.

The key rule for odr-used variables, the "One Definition Rule" is in 3.2/3:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

The "no diagnostic required" part means that programs violating this rule cause undefined behavior, which may range from failing to compile, compiling and failing in surprising ways to compiling and acting as if everything was OK. And your compiler need not warn you about the problem.

The reason, as others have already indicated, is that many of these violations would only be detected by a linker. But optimizations may have removed references to objects, so that no cause for linkage failure remains or else linking may sometimes occur only at runtime or be defined to pick an arbitrary instance from multiple definitions of a name.

于 2013-01-27T15:34:23.237 回答