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

时间:2014-05-02 12:53:37

标签: 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-used


注意: 这个问题代表a debate in the Lounge的一个不那么炽热/不合逻辑/无穷无尽的版本。

3 个答案:

答案 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的措辞):

  

变量x [在我们的例子中,A::a],其名称显示为潜在评估的表达式ex [在我们的例子中, id-expression {{1除非

,否则 odr-used      
      
  • 将左值到右值的转换应用于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。这里没有左值到右值的转换,所以仍然没有违反“出现在常量表达式中的要求”。