Lig*_*ica 15 c++ language-lawyer constexpr c++11
给出以下代码:
struct A { static constexpr int a[3] = {1,2,3}; };
int main () {
int a = A::a[0];
int b [A::a[1]];
}
Run Code Online (Sandbox Code Playgroud)
是A::a必然使用的ODR的int a = A::a[0]?
注意:这个问题代表了休息室辩论的一个不那么闷热/不合逻辑/无穷无尽的版本.
Ric*_*ith 20
A::a:int a = A::a[0];
Run Code Online (Sandbox Code Playgroud)
的初始化是常量表达式,但是这并不能阻止A::a被ODR使用的在这里.而且,事实上,A::a是ODR使用的这个表达.
从表达式开始A::a[0],让我们一起浏览[basic.def.odr](3.2)/ 3(对于未来的读者,我正在使用N3936中的措辞):
变量
x[在我们的例子中A::a],其名称显示为ex [在我们的例子中,id-expressionA::a] 的潜在评估表达式是odr-used,除非
应用左值到右值转换
x产生一个常量表达式[它确实]不会调用任何非平凡的函数[它没有]和,如果
x是一个对象[它],
ex是表达式的潜在结果集合的元素e,其中左值e或右值转换应用于或者e是丢弃值表达式.
那么:有什么可能的价值观e?表达式的潜在结果集是表达式的一组子表达式(您可以通过阅读[basic.def.odr](3.2)/ 2来检查这一点),因此我们只需要考虑其表达式ex是子表达式.那些是:
A::a
A::a[0]
Run Code Online (Sandbox Code Playgroud)
其中,左值到右值的转换不会立即应用A::a,因此我们只考虑A::a[0].每[basic.def.odr](3.2)/ 2,该组的潜在结果A::a[0]是空的,所以A::a被ODR使用的由该表达.
现在,你可以说我们先重写A::a[0]一下*(A::a + 0).但是,这改变不了什么:可能的值,e然后
A::a
A::a + 0
(A::a + 0)
*(A::a + 0)
Run Code Online (Sandbox Code Playgroud)
其中,只有第四个应用了左值到右值的转换,同样,[basic.def.odr](3.2)/ 2表示潜在结果的集合*(A::a + 0)是空的.特别要注意的是,数组到指针的衰减不是左值到右值的转换([conv.lval](4.1)),即使它将数组左值转换为指针右值 - 它是一个数组到 -指针转换([conv.array](4.2)).
A::a:int b [A::a[1]];
Run Code Online (Sandbox Code Playgroud)
根据标准,这与第一种情况没有什么不同.同样,A::a[1]是一个常量表达式,因此这是一个有效的数组绑定,但仍允许编译器在运行时发出代码来计算此值,并且数组绑定仍然使用 A::a.
特别注意,常量表达式是(默认情况下)可能被评估的表达式.Per [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是未评估的操作数.没有其他类型的未评估操作数.
A::a是有用的.在C++ 11中,相关的措辞是3.2p2 [basic.def.odr]:
[...]一个名称显示为潜在评估表达式的变量是odr-used,除非它是一个满足出现在常量表达式(5.19)中的要求的对象,而左值到右值的转换(4.1)是立即申请.[...]
变量的名称A::a出现在声明int a = A::a[0]中的full-expression中A::a[0],这是一个可能被评估的表达式.A::a是:
但是,左值到右值的转换不会立即应用于A::a; 它适用于表达式A::a[0].实际上,左值到右值的转换可能不适用于数组类型的对象(4.1p1).
因此使用的A::a是odr.
从C++ 11开始,规则已经有所扩展. DR712 是条件表达式的整数常量操作数"使用?" 介绍了表达式的潜在结果集的概念,它允许表达式,例如x ? S::a : S::b避免使用odr.然而,虽然潜在结果集合尊重诸如条件运算符和逗号运算符之类的运算符,但它不尊重索引或间接; 所以在目前的C++ 14草案(截至日期为n3936)中A::a仍然使用了.
[我相信这是理查德史密斯的答案的浓缩等价物,然而它没有提到自C++ 11以来的变化.]
At 是C++ 14中使用的变量odr?我们将讨论这个问题以及对3.2节的可能措辞更改,以允许索引或间接数组以避免使用odr.
不,它不是odr-used 的。
\n\n首先,您的数组及其元素都是文字类型:
\n\n\n\n\n\n\n
[C++11: 3.9/10]:如果某个类型满足以下条件,则该类型是文字类型:\n
\n- 标量类型;或者
\n- 类类型(第 9 条)
\n- 一个简单的复制构造函数,
\n- 没有不平凡的移动构造函数,
\n- 一个简单的析构函数,
\n- 一个简单的默认构造函数或至少一个除复制或移动构造函数之外的 constexpr 构造函数,以及
\n- 所有非静态数据成员和文字类型的基类;或者
\n- 文字类型的数组。
\n
现在我们查找一下odr使用的规则:
\n\n\n\n\n\n
[C++11: 3.2/2]:[..]名称显示为潜在计算表达式的变量或非重载函数是odr 使用的,除非它是满足\xef\xac\x81es 出现在常量表达式 (5.19) 中的要求的对象,并且立即应用左值到右值的转换(4.1)。[..]
在这里我们提到了常量表达式的规则,其中不包含任何禁止初始化程序成为常量表达式的内容;相关段落是:
\n\n\n\n\n\n\n
[C++11: 5.19/2]:条件表达式是常量表达式,除非它涉及以下内容之一作为潜在计算的子表达式[..]:\n
\n- [..]
\n- 左值到右值的转换 (4.1),除非它应用于\n
\n\n
- 整数或枚举类型的泛左值,引用具有前面初始化的非易失性 const 对象,并使用常量表达式进行初始化,或者
\n- 文字类型的泛左值,引用用 定义的非易失性对象
\nconstexpr,或者引用此类对象的子对象,或者- 文字类型的泛左值,引用用常量表达式初始化的非易失性临时对象;
\n- [..]
\n
(不要被产生式的名称“条件表达式”所困扰:它是常量表达式的唯一产生式,因此也是我们正在寻找的产生式。)
\n\n然后,考虑A::a[0]to的等价性*(A::a + 0),在数组到指针的转换之后,您将得到一个右值:
\n\n\n\n
[C++11: 4.2/1]:“数组NT”或“未知边界数组T”类型的左值或右值可以转换为“指向T”的指针类型的纯右值。结果是指向数组第一个元素的指针。
然后对该右值执行指针算术,结果也是一个右值,用于初始化a。这里没有任何左值到右值的转换,因此仍然没有违反“出现在常量表达式中的要求”。
| 归档时间: |
|
| 查看次数: |
1336 次 |
| 最近记录: |