这可能是一个不寻常的问题,因为它要求对another question的简短回答以及与之相关的C ++ 11标准的某些方面进行更全面的解释。
为便于参考,我将在此总结引用的问题。 OP定义了一个类:
struct Account
{
static constexpr int period = 30;
void foo(const int &) { }
void bar() { foo(period); } //no error?
};
并且想知道为什么他没有得到关于他使用类内初始化静态数据成员的错误(一本书提到这是非法的)。 Johannes Schaub的回答指出:
尽管我依赖这个答案的来源和有效性,但老实说我不喜欢它,因为我个人觉得它太神秘了,所以我试图找到一个更有意义的答案,只取得部分成功。相关似乎是§9.4.2/ 4:
“程序中只有一个静态数据成员的定义 odr-used (3.2); 不需要诊断”< / em> [重点是我的]
这让我更接近这一点。这就是§3.2/ 2定义 odr-used 变量的方式:
“名称显示为潜在评估表达式的变量使用odr ,除非它是满足出现在常量表达式中的要求的对象(5.19)并且立即应用左值到右值的转换(4.1)“ [Emphases are mine]
在OP的问题中,变量period
显然满足出现在常量表达式中的要求,即constexpr
变量。因此必须在第二条件中找到原因:“,并且立即应用左值到右值转换(4.1)”。
这是解释标准的麻烦。 这第二个条件究竟意味着什么?它涵盖了哪些情况?这是否意味着静态constexpr
变量不 odr-used(因此可以在类中初始化),如果它是从函数返回的?
更一般地说:您可以对静态constexpr
变量做什么,以便您可以在课堂初始化它?
答案 0 :(得分:3)
这是否意味着静态的constexpr变量不会被使用(和 因此,如果从a返回,则可以在类中初始化) 功能
是
基本上,只要您将其视为值,而不是对象,那么它就不会被使用。考虑一下,如果粘贴在值中,代码将以相同的方式运行 - 这是在它被视为右值时。但有些情况下它不会。
只有少数情况下,对基元执行左值到右值转换不,而且这是参考绑定&obj
,可能还有其他一些,但它很少。请记住,如果编译器为您提供const int&
引用period
,那么您必须能够获取其地址,此外,此地址必须与每个TU 相同 >。这意味着,在C ++可怕的TU系统中,必须有一个明确的定义。
如果没有使用odr,编译器可以在每个TU中复制,或者替换它或者它想要的任何东西,你就无法观察到差异。
答案 1 :(得分:3)
你错过了前提的一部分。如果您还在某处定义Account::period
(但不提供初始化程序),则上面的类定义完全有效。见9.4.2 / 3的最后一期:
如果成员使用了odr,则仍应在命名空间范围内定义该成员 (3.2)在程序和命名空间范围定义中不得 包含初始化程序。
整个讨论仅适用于静态constexpr数据成员,而不适用于名称空间范围的静态变量。当静态数据成员是constexpr
(而不是简单的const)时,必须在类定义中初始化它们。所以你的问题应该是:你有什么准备使用静态constexpr数据成员,这样你就不必在命名空间范围内为它提供定义?
根据前面的说法,答案是该成员不得 odr-used 。您已经找到了 odr-used 定义的相关部分。因此,您可以在不是潜在评估的上下文中使用该成员 - 作为未评估的操作数(例如sizeof
或decltype
)或其子表达式。您可以在立即应用左值到右值转换的表达式中使用它。
现在我们归结为对变量的使用会导致右值转换的左值?
答案的一部分在§5/ 8中:
每当glvalue表达式作为运算符的操作数出现时 期望该操作数的prvalue,左值到右值(4.1), 数组到指针(4.2)或函数到指针(4.3)标准 应用转换以将表达式转换为prvalue。
对于基本上适用于应用标准算术转换的所有运算符的算术类型。因此,您可以在不需要定义的情况下在各种算术和逻辑运算中使用该成员。
我无法枚举所有事情,你可以或不可以在这里做,因为某些东西是[g]左值或[p]右值的要求分散在整个标准中。经验法则是:如果仅使用变量的值,则应用左值转换的左值;如果变量用作对象,则将其用作左值。
您不能在明确需要左值的上下文中使用它,例如作为address-of运算符或变异运算符的参数。左值引用的直接绑定(没有转换)就是这样的上下文。
更多例子(没有详细的标准分析):
您可以将它传递给函数,除非函数参数是可以直接绑定变量的引用。
从函数返回它:如果隐式转换为返回类型涉及用户定义的转换函数,则传递给函数的规则适用。否则你可以返回它,除非函数返回一个直接引用变量的左值(引用)。
odr-used 变量的关键规则,“一个定义规则”在3.2 / 3中:
每个程序都应包含每个非内联的一个定义 在该程序中使用的函数或变量;没有诊断 必需的。
“无需诊断”部分意味着违反此规则的程序会导致未定义的行为,其范围可能从未能编译,编译和失败,以令人惊讶的方式编译和操作就像一切正常。而且你的编译器不需要警告你这个问题。
正如其他人已经指出的那样,其中许多违规行为只会被链接器检测到。但是优化可能已经删除了对对象的引用,因此不会导致链接失败,或者链接有时只能在运行时发生,或者被定义为从名称的多个定义中选择任意实例。