const和constexpr数组的区别

10

为什么在数组使用时 constconstexpr 会有区别?

int const xs[]{1, 2, 3};
constexpr int ys[]{1, 2, 3};

int as[xs[0]];  // error.
int bs[ys[0]];  // fine.

我希望xs [0]ys [0]都被视为常量表达式,但只有后者被视为常量表达式。


constexpr 值可以在编译时计算,而 const 则不行。 - teh internets is made of catz
3
一个const数组不需要有可见的定义(它可以是extern或稍后在文件中定义),这就是为什么会有区别。至于为什么有这个区别,我怀疑标准本来可以规定:当一个定义存在且对于constexpr是有效的(就像你代码中的情况一样),那么constconstexpr一样好,因为此时信息可用于评估它。我不知道为什么标准没有这么说,但是也许一个好理由是:"如果你想让你的数组是constexpr,那就直接说吧"。 - Steve Jessop
一个可能对你的问题有补充的点是,为什么 int const x = 5;constexpr int y = 5 都可以作为数组声明的固定大小 (int a[x], b[y]),但在被不同对待的数组内部(这涉及到 Steve 上面的评论)。 - WhozCraig
@SteveJessop:这仍然会有点复杂。constexpr 需要用其他 constexpr 初始化,而 const 数组则可以使用非 constexpr 来初始化,比如一个函数 f()const T array[] = { f(1), f(2), f(3) }; 将是动态初始化,而不是静态(constexpr)初始化。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas:这就是“定义存在且对于constexpr有效”的意思。就像你所说的,为了触发特殊处理,该定义需要能够作为constexpr进行评估。 - Steve Jessop
2个回答

5

一条较长的社区百科评论。


表达式xs [0]在[expr.sub] / 1中定义为*((xs)+(0))。 (请参见下面的括号。)

其中一个表达式应具有“指向T的指针”类型,另一个应具有未作用域的枚举或整数类型。

因此应用数组到指针转换[conv.array]:

N T的数组”或“未知边界的T的数组”的lvalue或rvalue可以转换为类型为“指向T的指针”的prvalue。结果是数组的第一个元素的指针。

请注意它可以对lvalue进行操作,结果是一个prvalue0作为整数字面值也是一个prvalue。加法在[expr.add] / 5中定义。由于两个都是prvalues,因此不需要进行lvalue到rvalue的转换。

int arr[3];
constexpr int* p = arr;  // allowed and compiles

关键步骤现在似乎是间接引用* [ expr.unary.op ] / 1
一元操作符*执行间接引用:应用于其上的表达式应为指向对象类型或函数类型的指针,并且结果是指向表达式所指对象或函数的左值。因此,xs [0]的结果是指向xs数组的第一个元素的左值,类型为int const
注意:[expr.prim.general] / 6
带括号的表达式是主表达式,其类型和值与括号内表达式的类型和值相同。括号的存在不影响表达式是否为左值。
如果我们现在查看[expr.const] / 2中禁止出现某些表达式和转换的项目符号,唯一适用的项目符号(据我所知)是从左值到右值的转换:
左值到右值转换(4.1),除非它应用于
具有前导初始化、使用常量表达式初始化的非易失性整数或枚举类型的非易失性glvalue,[注:字符串字面量(2.14.5)对应于这些对象的数组。—end note] 或
引用constexpr定义的非易失性对象的文字类型的非易失性glvalue,或引用此类对象的子对象,或
但在评估xs [0]时出现的唯一真正的左值到右值转换是从指向第一个元素的结果左值转换而来。
对于OP中的示例:
int const xs[]{1, 2, 3};
int as[xs[0]];  // error.

这个元素xs[0]具有非易失性的const整型,它的初始化在出现常量表达式的前面,并且已经用一个常量表达式进行了初始化。


顺便提一句,在引用的[expr.const]/2段中添加的"注释"已被添加以澄清以下合法:

constexpr char c = "hello"[0];

请注意,字符串字面量也是左值。
如果有人能解释为什么在常量表达式中不允许出现xs[0],那就太好了。

实现拒绝此操作的原因是因为它们不认为这是段落的意图(意图是与C++03向后兼容,而C++03不允许const int a = 0; int x[a];)。C++03也不允许"foo"[0],但是这个段落也允许了它,这是草案后来的“让我们保持非规范性编辑”的变更,因为从字符串字面量中读取被认为是可取的。实际上,bullet1的真正意图似乎是“允许从字符串字面量中读取,并保持与C++03向后兼容”。 - Johannes Schaub - litb
(以上评论基于与实现clang的Richard Smith的早期交谈。) - Johannes Schaub - litb
@JohannesSchaub-litb,我不太确定我完全理解了你的解释。在我所知的C++03中,“const int a = 1; int x [a];”是允许的,而大小为零的数组在C++03和C++11中都是禁止的。此外,放宽对“const”变量出现在常量表达式中的限制如何破坏向后兼容性?或者这是否意味着意图并非要为“const”变量更改任何内容? - dyp
是的,那是两个错误。上面应该是“而且C++03确实允许const int a = 1; int x[a];”。在C++11中不允许这样做将破坏向后兼容性。 - Johannes Schaub - litb

0

C++11中使用constexpr来使表达式在编译时被求值,与const关键字不同。

constexpr int ys[]{1, 2, 3};在编译时被求值,因此没有错误

当使用ys[0]时。

另外,请注意这里使用了C++11统一初始化方式{}

其他例子:

constexpr int multipletwo(int x)
{
return 2*x;
}

int num_array[multipletwo(3)]; //No error since C++11, num_array has 6 elements.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接