给出以下代码:

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

int main () {
  int a = A::a[0];
  int b  [A::a[1]];
}


A::a是否一定在int a = A::a[0]中是odr-used



注意:此问题表示a debate in the Lounge的较不易理解/不合逻辑/无尽的版本。

最佳答案

第一次使用A::a

int a = A::a[0];


初始值设定项是一个常量表达式,但这并不能阻止A::a在此处被奇特使用。实际上,A::a被该表达式使用。

从表达式A::a[0]开始,让我们遍历[basic.def.odr](3.2)/ 3(对于以后的读者,我使用的是N3936的措辞):


  变量x [在我们的情况下为A::a]的名称显示为一个可能评估的表达式,例如[在本例中,id-expression A::a]除非使用,否则
  
  
  将左值到右值转换应用于x会产生一个常量表达式[确实如此]
  不会调用任何非平凡的函数[不会],并且,
  如果x是一个对象[它是],
  
  
  ex是表达式e的一组可能结果的元素,其中将左值到右值转换应用于e,或者e是舍弃值表达式。
  
  


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

A::a
A::a[0]


其中,左值到右值转换不会立即应用于A::a,因此我们仅考虑A::a[0]。根据[basic.def.odr](3.2)/ 2,A::a[0]的潜在结果集为空,因此该表达式奇数使用A::a

现在,您可能会说我们首先将A::a[0]重写为*(A::a + 0)。但这并没有改变:e的可能值是

A::a
A::a + 0
(A::a + 0)
*(A::a + 0)


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

第二次使用A::a

int b  [A::a[1]];


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

特别要注意的是,常量表达式(默认情况下)是可能求值的表达式。根据[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的操作数是未求值的操作数。没有其他类型的未评估操作数。

09-10 04:25
查看更多