【问题标题】:What am I allowed to do with a static, constexpr, in-class initialized data member?我可以对静态、constexpr、类内初始化的数据成员做什么?
【发布时间】:2013-01-10 23:05:36
【问题描述】:

这可能是一个不寻常的问题,因为它要求对another question 的简短回答以及与之相关的 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-used (3.2) 的静态数据成员应该只有一个定义;不需要诊断" [重点是我的]

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

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

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

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

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

【问题讨论】:

  • 我看到我们有很多relevant yet distinct questions。两者都来自相同的基础 Q 和答案 :)
  • @AlokSave:确实如此。至少我不是唯一一个觉得整个事情有点混乱的人。
  • 老实说,为了理解标准,我做了一些挠头,但我无法真正看透它。实际上,您在整个过程中都很好地表达了思考过程question.Me 我提出了一个更笼统的问题,担心我对标准语的看法可能会影响阅读它的人是否有意义。 +1 用于记录您的研究:)
  • @AlokSave:我喜欢“担心我对标准语的看法实际上可能会影响阅读它的人的意义”部分。几乎每次我不得不询问一些对我来说晦涩难懂的正式定义的含义时(不仅仅是在 C++ 或编程中),我都会遇到这种感觉。
  • 我没有将“神秘”术语“一个定义规则”放入我的答案中。只有当有人出于某种原因想知道我提到的规则在标准中的位置时,我才提到它。

标签: c++ c++11 static-members constexpr one-definition-rule


【解决方案1】:

这是否意味着静态 constexpr 变量没有被 ODR 使用(并且 因此可以在类内初始化)如果它是从 功能?

是的。

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

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

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

【讨论】:

  • 我理解精神和基本原理,+1ed,但我对技术性仍有一些疑问。这是否意味着这将是合法的:foo(static_cast<int&&>(period))?
  • 呃,其实我也不知道。如果在这种情况下发生左值到右值,并且它相当模糊,所以我不确定它是否存在(不要这么认为,你必须检查标准),那么它是合法的,如果不是,那么它是不,因为右值 ref 不会绑定到 const 左值。
  • 在这种情况下,static_cast 的输出似乎是一个prvalue,这意味着它是合法的。根据 5.2.9/1:“表达式 static_cast(v) 的结果是将表达式 v 转换为 T 类型的结果。如果 T 是左值引用类型或对函数类型的右值引用,结果是左值;如果 T 是对对象类型的右值引用,则结果是 xvalue;否则,结果是 prvalue".
  • 这里如果 T 是对对象类型的右值引用,则结果是 xvalue; 适用(int 是对象类型)。 xvalue 是glvalue,而不是prvalue,因此不会发生从左值到右值的转换。 (强制转换为右值引用的要点是,您可以在不复制该值的情况下威胁原始对象为右值。
  • 先写“是”:对于返回 int const& 或返回类类型的函数,答案是“否”,该类类型有一个带有 int const& 参数的转换构造函数。跨度>
【解决方案2】:

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

如果成员是 odr-used,则仍应在命名空间范围内定义 (3.2) 在程序和命名空间范围定义中不得 包含一个初始化程序。

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

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

所以现在我们要了解变量的哪些用途会导致立即左值到右值的转换?

部分答案在 §5/8 中:

当一个泛左值表达式作为一个运算符的操作数出现时 期望该操作数的纯右值,左值到右值(4.1), 数组到指针 (4.2) 或函数到指针 (4.3) 标准 应用转换将表达式转换为纯右值。

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

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

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

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

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

对于从函数返回它:如果隐式转换到返回类型涉及用户定义的转换函数,则适用于传递给函数的规则。否则你可以返回它,除非函数返回一个直接引用变量的左值(引用)。

odr-used 变量的关键规则,“一个定义规则”在 3.2/3 中:

每个程序都应该包含每个非内联的定义 在该程序中使用 odr 的函数或变量;没有诊断 必填。

“不需要诊断”部分意味着违反此规则的程序会导致未定义的行为,其范围可能从无法编译、编译和以令人惊讶的方式失败到编译并表现得好像一切正​​常。你的编译器不需要警告你这个问题。

正如其他人已经指出的那样,原因是其中许多违规行为只能由链接器检测到。但是优化可能已经删除了对对象的引用,因此不会导致链接失败,否则链接有时可能仅在运行时发生,或者被定义为从名称的多个定义中选择任意实例。

【讨论】:

    猜你喜欢
    • 2013-10-19
    • 2016-10-15
    • 2018-11-11
    • 2012-07-16
    • 2023-03-18
    • 1970-01-01
    • 2021-08-07
    • 1970-01-01
    相关资源
    最近更新 更多