下标时,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的措辞):
一个variables
x
[在我们的例子中,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)/ 2 , A::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)。 未评估的操作数未被评估。
这些小节分别说明了一些typeid
expression式的操作数, 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::a
是odr使用的 。
自从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式中的要求”。