C++ 订阅时是否必须使用constexpr数组?

C++ 订阅时是否必须使用constexpr数组?,c++,c++11,language-lawyer,constexpr,C++,C++11,Language Lawyer,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是否必须在int A=A::A[0]中 注意:这个问题代表的是一个不太激烈/不合逻辑/无休止的版本。不,它不是odr 首先,数组及其元素都是文本类型: [C++11:3.9/10]:如果类型是: 标量类型;或 类类型从句9 一个微不足道的复制构造函数, 没有非

给定以下代码:

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

首先,数组及其元素都是文本类型:

[C++11:3.9/10]:如果类型是:

标量类型;或 类类型从句9 一个微不足道的复制构造函数, 没有非平凡的移动构造函数, 一个微不足道的析构函数, 普通默认构造函数或至少一个除复制或移动构造函数以外的constexpr构造函数,以及 文字类型的所有非静态数据成员和基类;或 文本类型的数组。 现在我们查找odr使用的规则:

[C++11:3.2/2]:[…]如果变量或非重载函数的名称显示为可能的计算表达式,则使用odr,除非该函数是满足常量表达式5.19中出现要求的对象,并且立即应用左值到右值转换4.1。[……]

这里我们提到了关于常量表达式的规则,其中没有任何内容禁止初始值设定项成为常量表达式;有关段落如下:

[C++11:5.19/2]:条件表达式是一个常量表达式,除非它包含以下其中一项作为潜在的求值子表达式[…]:

[..] 左值到右值的转换4.1,除非应用于 整型或枚举类型的glvalue,它引用具有先前初始化、使用常量表达式初始化或 文字类型的glvalue,指用constexpr定义的非易失性对象,或指此类对象的子对象,或 一个文本类型的glvalue,它引用一个用常量表达式初始化的非易失性临时对象; [..] 不要因为产生式的名称而感到不快,条件表达式:它是常量表达式的唯一产生式,因此是我们正在寻找的

然后,考虑A::A[0]与*A::A+0的等价性,在数组到指针的转换之后,您有一个右值:

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

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

首先使用a::a: 初始值设定项是一个常量表达式,但这并不能阻止在这里使用a::a作为odr。事实上,A::A是这个表达式使用的odr

从表达式A::A[0]开始,让我们浏览一下[basic.def.odr]3.2/3,我将使用N3936中的措辞:

odr使用的变量x[在本例中为A::A],其名称显示为可能计算的表达式ex[在本例中为id表达式A::A]

将左值到右值的转换应用于x会产生一个常量表达式[它确实如此] 不会调用任何非平凡函数,而且

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

ex是表达式e的潜在结果集的一个元素,其中左值到右值的转换应用于e,或者e是丢弃的值表达式。 那么:e的可能值是什么?表达式的一组潜在结果是一组表达式的子表达式,可以通过读取[Basic .DEF.ODR] 3.2/2来检查,因此我们只需要考虑哪个EX是子表达式的表达式。这些是:

A::a
A::a[0]

中,LVal-RValo转换不立即应用到A:A,因此我们只考虑A::A(0)。根据[basic.def.odr]3.2/2,A::A[0]的潜在结果集为空,因此A::A是此表达式使用的odr

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

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

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

请特别注意,常量表达式在默认情况下可能是经过计算的表达式。每[基本d ef.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的操作数是未赋值的操作数。没有其他类型的未赋值操作数。

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

[…]除非是满足常量表达式5.19中出现要求的对象,并且立即应用左值到右值转换4.1,否则将使用名称显示为潜在计算表达式的变量odr。[……]

变量A::A的名称出现在声明int A=A::A[0]中的完整表达式A::A[0]中,该表达式可能是一个经过计算的表达式。A::A是:

物件 满足在常量表达式中出现的要求 但是,左值到右值的转换不会立即应用于A::A;它应用于表达式A::A[0]。实际上,左值到右值的转换可能不适用于数组类型为4.1p1的对象

所以使用了A::A odr

自C++11以来,规则有所扩展。是否使用条件表达式的整数常量操作数?介绍表达式的潜在结果集的概念,它允许使用诸如x?S::a:S::b以避免使用odr。然而,尽管潜在结果集考虑了条件运算符和逗号运算符等运算符,但它不考虑索引或间接寻址;因此,截至目前为止,A::A仍在C++14 n3936的当前草案中使用

[我认为这与Richard Smith的答案是一个简明的等价物,但是没有提到自C++11以来的变化。]


在会议上,我们讨论了这个问题以及可能对第3.2节的措辞进行的修改,以允许对数组进行索引或间接寻址,从而避免odr的使用。

@Yakk:^7:订阅者对订阅者密码的任何使用或根据订阅者密码采取的任何行动全权负责,并对通过订阅者帐户进行的所有活动承担全部责任,并同意特此免除网络和堆栈交换对此类活动的任何和所有责任。订户同意立即通知Stack Exchange订户帐户或密码的任何实际或可疑丢失、被盗或未经授权的使用。ToS中没有任何内容禁止我向我选择的任何人类、猫科动物或火神授予该授权:-@尼科斯:不能对同一个账户的帖子进行否决投票,我的猫还不到13岁,所以必须按照上面链接的ToS中的1个分享我的帖子:-@MWid:literal类型的glvalue是指用constexpr定义的非易失性对象,还是指此类对象的子对象,不能恰当地描述所述转换?@LightnessRacesinOrbit I因答案不正确而被否决。在C++11规则中,左值到右值的转换不会立即应用于变量,因此它是odr-used;在这种情况下,您的推理中的缺陷是,正如Richard Smith所说,左值到右值的转换没有立即应用于变量A::A。如果您还可以提供一些基本原理,那就太好了:为什么某些似乎是编译时常量的东西需要定义?INTB[A::A[1]]是怎样的;与结构A不同{static constexpr auto A=5;};intb[A::A]@dyp我希望差异就在规范的字母中,因为它在这里被复制。没别的了。对我来说似乎是一个合乎逻辑的解释。@dyp我认为我们在讨论成为[basic.def.odr]p2的措辞时并没有真正考虑到这一点,但对我来说,保持这些规则的简单显然是有意义的。为了支持当前的odr使用规则,实现必须推迟某些操作;如果我们将数组索引添加到混合中,实现还必须推迟将[I]转换为*A+I,因为它们的行为会有所不同。我们已经通过使RVal[I]成为xValk冒险进入这些水域,所以也许现在是考虑这一变化的更好时机……RICHARTHOMEN:个人而言,我不明白为什么A+I也需要ODR的使用。表达式的运行时地址与表达式的结果无关。当然,标准是否支持它是另一个问题,但从概念上讲,我认为不应该使用ODR。你能澄清一件事吗?您说表达式的潜在结果集是该表达式的子表达式集,然后您说a::a是a::a[0]的子表达式,也是a::a的子表达式。之后,您说A::A[0]的潜在结果集为空,但您不是说A::A是一个子表达式,因此是一个潜在结果o吗 fa::A[0]?为什么现在是空的?
A::a
A::a[0]
A::a
A::a + 0
(A::a + 0)
*(A::a + 0)
int b  [A::a[1]];