给出以下代码:
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的一个不那么炽热/不合逻辑/无穷无尽的版本。
答案 0 :(得分:20)
A::a
:int a = A::a[0];
初始化程序是一个常量表达式,但这并不会阻止A::a
odr-used 。事实上,这个表达式A::a
是 odr-used 。
从表达式A::a[0]
开始,让我们逐步浏览 [basic.def.odr](3.2)/ 3 (对于未来的读者,我使用的是来自N3936的措辞):
变量
,否则 odr-usedx
[在我们的例子中,A::a
],其名称显示为潜在评估的表达式ex [在我们的例子中, id-expression {{1除非
将左值到右值的转换应用于
A::a
会产生一个常量表达式[它确实] 这不会调用任何非平凡的函数[它没有]和如果
x
是对象[它是],
x
是表达式ex
的潜在结果集的元素,其中左值到右值的转换应用于e
,或e
是一个废弃值表达式。
那么:e
有哪些可能的值?表达式的潜在结果的集合是表达式的一组子表达式(您可以通过阅读 [basic.def.odr](3.2)/ 2 来检查这一点。 ),所以我们只需要考虑e
是子表达式的表达式。那些是:
ex
其中,左值到右值的转换不立即应用于A::a
A::a[0]
,因此我们只考虑A::a
。根据 [basic.def.odr](3.2)/ 2 ,A::a[0]
的潜在结果集为空,因此A::a[0]
odr-used 通过这个表达。
现在,您可以说我们首先将A::a
重写为A::a[0]
。但这没有任何改变:*(A::a + 0)
的可能值是
e
其中,只有第四个应用了左值到右值的转换,而 [basic.def.odr](3.2)/ 2 表示这组潜在结果A::a
A::a + 0
(A::a + 0)
*(A::a + 0)
的内容为空。特别要注意的是,数组到指针的衰减是而不是左值到右值的转换( [conv.lval](4.1)),即使它转换为数组左值到指针右值 - 它是一个数组到指针的转换( [conv.array](4.2))。
*(A::a + 0)
:A::a
根据标准,这与第一种情况没有什么不同。同样,int b [A::a[1]];
是一个常量表达式,因此这是一个有效的数组绑定,但仍允许编译器在运行时发出代码来计算此值,并且数组绑定仍然 odr-uses A::a[1]
。
特别注意,常量表达式(默认情况下)是可能被评估的表达式。每 [basic.def.odr](3.2)/ 2 :
表达式可能被评估,除非它是未评估的操作数(第5条)或其子表达式。
[expr](5)/ 8 只是将我们重定向到其他子条款:
在某些情况下,出现未评估的操作数(5.2.8,5.3.3,5.3.7,7.1.6.2)。未评估未操作的操作数。
这些子条款(分别)表示某些A::a
表达式的操作数,typeid
的操作数,sizeof
的操作数和noexcept
的操作数未被评估操作数。没有其他类型的未评估操作数。
答案 1 :(得分:6)
A::a
odr-used 。在C ++ 11中,相关的措辞是3.2p2 [basic.def.odr] :
[...]一个名称显示为潜在评估表达式的变量是 odr-used ,除非它是满足出现在常量表达式(5.19)和立即应用左值到右值的转换(4.1)。 [...]
变量A::a
的名称出现在声明int a = A::a[0]
中,在完整表达式A::a[0]
中,这是一个可能被评估的表达式。 A::a
是:
但是,左值到右值的转换不立即应用于A::a
;它应用于表达式A::a[0]
。实际上,左值到右值的转换可能不适用于数组类型的对象(4.1p1)。
所以A::a
odr-used 。
自C ++ 11以来,规则已经有所扩大。 DR712 是否使用了条件表达式的整数常量操作数?“引入了潜在结果集的概念表达式,允许x ? S::a : S::b
等表达式避免使用 odr-use 。但是,虽然潜在结果集尊重诸如条件运算符和逗号运算符之类的运算符,但它不尊重索引或间接;所以A::a
在C ++ 14的当前草案中仍然是 odr-used (截止日期为n3936)。
[我相信这是理查德史密斯的答案的缩写,但是没有提到自C ++ 11以来的变化。]
在When is a variable odr-used in C++14?,我们讨论了这个问题以及对第3.2节可能的措辞更改,以允许索引或间接数组以避免 odr-use 。
答案 2 :(得分:1)
不,它不是 odr-used 。
首先,您的数组及其元素都是 literal 类型:
[C++11: 3.9/10]:
类型是文字类型,如果它是:
- 标量类型;或强>
- 带有
的班级类型(第9条)- 一个简单的复制构造函数,
- 没有非平凡的移动构造函数,
- 一个简单的析构函数,
- 除了复制或移动构造函数之外的普通默认构造函数或至少一个constexpr构造函数,并且
- 所有非静态数据成员和文字类型的基类;或
- 文字类型数组。
现在我们查看 odr-used 规则:
[C++11: 3.2/2]:
[..] 一个变量或非重载函数,其名称显示为可能已评估的表达式 odr-used ,除非它是一个满足出现在常数表达式(5.19)中的要求的对象,并立即应用左值到右值的转换(4.1)。 [..]
在这里,我们已经参考了常量表达式的规则,它们不包含任何禁止初始化器成为常量表达式的规则;相关段落是:
[C++11: 5.19/2]:
条件表达式是一个常量表达式,除非它涉及以下其中一项作为潜在评估的子表达式 [..] : 强>
- [..]
- 左值到右值的转换(4.1),除非它适用于
- 整数或枚举类型的glvalue,它引用具有前面初始化的非易失性const对象,用常量表达式初始化,或
- 文字类型的glvalue,指的是用
constexpr
定义的非易失性对象,或引用此类对象的子对象,或- 一个文字类型的glvalue,它引用一个用常量表达式初始化的非易失性临时对象;
- [..]
(不要被制作名称“条件表达式”推迟:它是常量表达式的唯一产生,因此,我们正在寻找。)
然后,考虑A::a[0]
到*(A::a + 0)
的等价性,在数组到指针转换之后,你有一个 rvalue :
[C++11: 4.2/1]:
“N
T
数组”或“T
未知范围数组”的左值或右值可以转换为类型“的prvalue”指向T
的指针。结果是指向数组的第一个元素的指针。
然后在此右值上执行指针算术,结果也是 rvalue ,用于初始化a
。这里没有左值到右值的转换,所以仍然没有违反“出现在常量表达式中的要求”。