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

给出以下代码:

struct A { static constexpr int a[3] = {1,2,3}; }; int main () { int a = A::a[0]; int b [A::a[1]]; } 

A::a必然odr-用于 int a = A::a[0]


注意:这个问题代表了rest室里一个不那么有争议/不合逻辑的辩论 。

首先使用A::a

 int a = A::a[0]; 

初始化器是一个常量expression式,但是这并不能阻止A::a在这里被使用 。 实际上,这个expression式使用A::a

从expression式A::a[0] ,让我们通过[basic.def.odr](3.2)/ 3 (对于未来的读者,我使用N3936的措辞):

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

  • 将左值到右值的转换应用于x产生一个不会调用任何非平凡函数的常量expression式,

  • 如果x是一个对象,

    • ex是expression式e潜在结果集合中的一个元素,其中将左值到右值转换应用于e ,或者e是丢弃值expression式。

那么: e有什么可能的价值呢? 一组expression式的潜在结果是一组expression式的子expression式(你可以通过阅读[basic.def.odr](3.2)/ 2 )来查看,所以我们只需要考虑ex是a的expression式子expression式。 那些是:

 A::a A::a[0] 

其中,左值到右值的转换不会立即应用到A::a ,所以我们只考虑A::a[0] 。 根据[basic.def.odr](3.2)/ 2A::a[0]的潜在结果集是空的,所以A::a被这个expression式使用。

现在,你可以争辩说,我们首先重写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)是空的。 特别要指出的是,尽pipe数组到指针的衰减不是左值到右值的转换( [conv.lval](4.1) ),即使它将数组左值转换为指针右值,指针转换( [conv.array](4.2) )。

第二次使用A::a

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

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

请特别注意,常量expression式(默认情况下)是潜在评估的expression式。 每[basic.def.odr](3.2)/ 2

除非是未评估的操作数(第5章)或其子expression式,否则expression式可能被评估

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

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

这些小节分别说明了一些typeidexpression式的操作数, sizeof的操作数, noexcept的操作数以及decltype的操作数是未noexcept的操作数。 没有其他types的未评估操作数。

是的, A::a是使用不当的

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

[…]名称显示为潜在评估expression式的variables是odr-used,除非它是满足出现在常量expression式(5.19)中的要求并且左值到右值转换(4.1)是立即申请。 […]

在完整expression式A::a[0] ,variablesA::a的名称出现在声明int a = A::a[0]中,这是一个潜在评估的expression式。 A::a是:

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

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

所以A::aodr使用的


自从C ++ 11以来,规则已经有所扩展。 DR712 是否使用条件expression式的整数常量操作数? 引入了一expression式的潜在结果的概念,它允许expression式如x ? S::a : S::b x ? S::a : S::b以避免臭味使用 。 然而,尽pipe潜在结果集合尊重条件运算符和逗号运算符等运算符,但它并不尊重索引或间接; 所以A::a在当前的C ++ 14草案(截至date为n3936)中仍然是odr使用的。

[我相信这是与理查德·史密斯的答案等同的,但是自从C ++ 11以来没有提到这种变化。]

在什么时候是一个variablesodr-在C ++ 14中使用? 我们将讨论这个问题,并在3.2节中对可能的措辞进行修改,以允许对数组进行索引或间接索引以避免使用臭名

不,它不是使用的

首先,你的数组和它的元素都是字面types的:

[C++11: 3.9/10]:一个types是一个文字types,如果它是:

  • 标量types; 要么
  • 一个类的类(第9条)与
  • 一个简单的复制构造函数,
  • 没有不平凡的移动构造函数,
  • 一个微不足道的析构函数,
  • 一个简单的默认构造函数或至less一个除复制或移动构造函数外的constexpr构造函数
  • 所有非静态数据成员和文字types的基类; 要么
  • 一个字面types的数组

现在我们查看odr-used规则:

[C++11: 3.2/2]: [..]一个variables或非重载的函数,其名称显示为潜在评估的expression式,除非它是满足出现在常量expression式中的要求的对象(5.19)并立即应用左值到右值的转换(4.1)。 [..]

在这里,我们已经提到了关于常量expression式的规则,其中不包含任何禁止您的初始化程序成为常量expression式的规则。 相关的段落是:

[C++11: 5.19/2]: 一个条件expression式是一个常量expression式,除非它涉及以下之一作为潜在的评估子expression式[..]

  • [..]
  • 一个左值到右值的转换(4.1),除非它适用于
    • 一个整数或枚举types的glvalue,它指的是一个非易失性const对象,具有前面的初始化,用一个常量expression式初始化,或者
    • 一个文字types的glvalue,指的是一个用constexpr定义的非易失性对象,或者指的是这样一个对象的一个​​子对象 ,或者
    • 一个文字types的glvalue,它是指用一个常量expression式初始化的非易失性临时对象;
  • [..]

(不要被生产的名字所忽略,“ 条件expression式 ”:它是唯一的常量expression式的产物,因此是我们正在寻找的那个)。

然后,考虑A::a[0]*(A::a + 0)的等价性,在数组到指针之间有一个右值

[C++11: 4.2/1]:将“ T数组”或“ T的未知的数组”的左值或右值转换为types“指向T指针”的值。 结果是一个指向数组的第一个元素的指针。

然后你的指针算术在这个右值执行,结果也是一个右值 ,用来初始化a 。 这里没有左值到右值的转换,所以仍然没有违反“出现在常量expression式中的要求”。