下载时是否必须使用constexpr数组?

Lig*_*ica 15 c++ language-lawyer constexpr c++11

给出以下代码:

struct A { static constexpr int a[3] = {1,2,3}; };

int main () {
  int a = A::a[0];
  int b  [A::a[1]];
}
Run Code Online (Sandbox Code Playgroud)

A::a必然使用的ODRint a = A::a[0]


注意:这个问题代表了休息室辩论的一个不那么闷热/不合逻辑/无穷无尽的版本.

Ric*_*ith 20

首次使用A::a:

int a = A::a[0];
Run Code Online (Sandbox Code Playgroud)

的初始化是常量表达式,但是这并不能阻止A::aODR使用的在这里.而且,事实上,A::aODR使用的这个表达.

从表达式开始A::a[0],让我们一起浏览[basic.def.odr](3.2)/ 3(对于未来的读者,我正在使用N3936中的措辞):

变量x[在我们的例子中A::a],其名称显示为ex [在我们的例子中,id-expression A::a ] 的潜在评估表达式是odr-used,除非

  • 应用左值到右值转换x产生一个常量表达式[它确实]不会调用任何非平凡的函数[它没有]和,

  • 如果x是一个对象[它],

    • ex是表达式的潜在结果集合的元素e,其中左值e或右值转换应用于或者e是丢弃值表达式.

那么:有什么可能的价值观e?表达式的潜在结果集是表达式的一组子表达式(您可以通过阅读[basic.def.odr](3.2)/ 2来检查这一点),因此我们只需要考虑其表达式ex是子表达式.那些是:

A::a
A::a[0]
Run Code Online (Sandbox Code Playgroud)

其中,左值到右值的转换不会立即应用A::a,因此我们只考虑A::a[0].每[basic.def.odr](3.2)/ 2,该组的潜在结果A::a[0]是空的,所以A::aODR使用的由该表达.

现在,你可以说我们先重写A::a[0]一下*(A::a + 0).但是,这改变不了什么:可能的值,e然后

A::a
A::a + 0
(A::a + 0)
*(A::a + 0)
Run Code Online (Sandbox Code Playgroud)

其中,只有第四个应用了左值到右值的转换,同样,[basic.def.odr](3.2)/ 2表示潜在结果的集合*(A::a + 0)是空的.特别要注意的是,数组到指针的衰减不是左值到右值的转换([conv.lval](4.1)),即使它将数组左值转换为指针右值 - 它是一个数组到 -指针转换([conv.array](4.2)).

二次使用A::a:

int b  [A::a[1]];
Run Code Online (Sandbox Code Playgroud)

根据标准,这与第一种情况没有什么不同.同样,A::a[1]是一个常量表达式,因此这是一个有效的数组绑定,但仍允许编译器在运行时发出代码来计算此值,并且数组绑定仍然使用 A::a.

特别注意,常量表达式是(默认情况下)可能被评估的表达式.Per [basic.def.odr](3.2)/ 2:

除非是未评估的操作数(第5条)或其子表达式,否则可能会对表达式进行求值.

[expr](5)/ 8只是将我们重定向到其他子条款:

在某些情况下,出现了未评估的操作数(5.2.8,5.3.3,5.3.7,7.1.6.2).未评估未评估的操作数.

这些子句称(分别)某些typeid表达式的操作数sizeof,操作数,操作数noexcept和操作数decltype是未评估的操作数.没有其他类型的未评估操作数.

  • @RichardSmith:就我个人而言,我也不明白为什么 `*(A + I)` 也需要 ODR 使用。A 的运行时地址与表达式的结果无关。当然,该标准是否支持它是另一个问题,但从概念上讲,我认为它不应该被 ODR 使用。 (2认同)

eca*_*mur 6

是的,A::a有用的.

在C++ 11中,相关的措辞是3.2p2 [basic.def.odr]:

[...]一个名称显示为潜在评估表达式的变量是odr-used,除非它是一个满足出现在常量表达式(5.19)中的要求的对象,而左值到右值的转换(4.1)是立即申请.[...]

变量的名称A::a出现在声明int a = A::a[0]中的full-expression中A::a[0],这是一个可能被评估的表达式.A::a是:

  • 一个东西
  • 满足出现在常量表达式中的要求

但是,左值到右值的转换不会立即应用于A::a; 它适用于表达式A::a[0].实际上,左值到右值的转换可能不适用于数组类型的对象(4.1p1).

因此使用的A::aodr.


从C++ 11开始,规则已经有所扩展. DR712 条件表达式的整数常量操作数"使用?" 介绍了表达式的潜在结果集的概念,它允许表达式,例如x ? S::a : S::b避免使用odr.然而,虽然潜在结果集合尊重诸如条件运算符和逗号运算符之类的运算符,但它不尊重索引或间接; 所以在目前的C++ 14草案(截至日期为n3936)中A::a仍然使用了.

[我相信这是理查德史密斯的答案的浓缩等价物,然而它没有提到自C++ 11以来的变化.]

At 是C++ 14中使用的变量odr?我们将讨论这个问题以及对3.2节的可能措辞更改,以允许索引或间接数组以避免使用odr.


Lig*_*ica 1

不,它不是odr-used 的

\n\n

首先,您的数组及其元素都是文字类型:

\n\n
\n

[C++11: 3.9/10]:如果某个类型满足以下条件,则该类型是文字类型:

\n\n
    \n
  • 标量类型;或者
  • \n
  • 类类型(第 9 条)
  • \n
  • 一个简单的复制构造函数,
  • \n
  • 没有不平凡的移动构造函数,
  • \n
  • 一个简单的析构函数,
  • \n
  • 一个简单的默认构造函数或至少一个除复制或移动构造函数之外的 constexpr 构造函数,以及
  • \n
  • 所有非静态数据成员和文字类型的基类;或者
  • \n
  • 文字类型的数组
  • \n
\n
\n\n

现在我们查找一下odr使用的规则:

\n\n
\n

[C++11: 3.2/2]: [..]名称显示为潜在计算表达式的变量或非重载函数是odr 使用的,除非它是满足\xef\xac\x81es 出现在常量表达式 (5.19) 中的要求的对象,并且立即应用左值到右值的转换(4.1)。[..]

\n
\n\n

在这里我们提到了常量表达式的规则,其中不包含任何禁止初始化程序成为常量表达式的内容;相关段落是:

\n\n
\n

[C++11: 5.19/2]: 条件表达式是常量表达式,除非它涉及以下内容之一作为潜在计算的子表达式[..]

\n\n
    \n
  • [..]
  • \n
  • 左值到右值的转换 (4.1),除非它应用于\n
      \n
    • 整数或枚举类型的泛左值,引用具有前面初始化的非易失性 const 对象,并使用常量表达式进行初始化,或者
    • \n
    • 文字类型的泛左值,引用用 定义的非易失性对象constexpr,或者引用此类对象的子对象,或者
    • \n
    • 文字类型的泛左值,引用用常量表达式初始化的非易失性临时对象;
    • \n
  • \n
  • [..]
  • \n
\n
\n\n

(不要被产生式的名称“条件表达式”所困扰:它是常量表达式的唯一产生式,因此也是我们正在寻找的产生式。)

\n\n

然后,考虑A::a[0]to的等价性*(A::a + 0),在数组到指针的转换之后,您将得到一个右值

\n\n
\n

[C++11: 4.2/1]:“数组N T”或“未知边界数组T”类型的左值或右值可以转换为“指向T”的指针类型的纯右值。结果是指向数组第一个元素的指针。

\n
\n\n

然后对该右值执行指针算术,结果也是一个右值,用于初始化a。这里没有任何左值到右值的转换,因此仍然没有违反“出现在常量表达式中的要求”。

\n

  • @LightnessRacesinOrbit 我投了反对票,因为答案不正确。在 C++11 规则中,左值到右值的转换不会“立即应用于”变量,因此它是 odr 使用的。 (5认同)
  • @Yakk:¶7:_“订阅者对订阅者密码下的任何使用或采取的行动承担全部责任,并对通过订阅者帐户进行的所有活动承担全部责任,并同意并特此免除网络和 Stack Exchange 的任何及所有责任订阅者同意立即通知 Stack Exchange 任何实际或疑似的丢失、被盗或未经授权使用订阅者帐户或密码的行为。”_ ToS 中的任何内容均不禁止我向我选择的任何人类、猫科动物或瓦肯人授予该授权。:-) (3认同)