【问题标题】:x[0] == 1 constant expression in C++11 when x is const int[]?当 x 为 const int[] 时,x[0] == 1 C++11 中的常量表达式?
【发布时间】:2013-09-25 00:51:24
【问题描述】:

以下 C++11 程序是否格式错误?

const int x[] = {1,2,3};

static_assert(x[0] == 1, "yay");

int main() {}

gcc 和 clang 似乎是这么认为的,但是为什么x[0] == 1 不是一个常量表达式呢?

x[0] == 1
subscript operator
*(x+0) == 1
array-to-pointer conversion (int* p = x)
*(p+0) == 1
pointer addition
*p == 1
indirection (lvalue y = x[0])
y == 1
lvalue-to-rvalue conversion:

整数(是的,它具有 const int 类型)或引用非易失性 const 对象的枚举类型(是的,它具有类型const int) 带有前面的初始化(是初始化为 1),初始化为常量表达式(是的 1 是常量表达式)

看起来不错,x 数组的第一个元素满足这些条件。

1 == 1

?

这是编译器错误、标准缺陷还是我遗漏了什么?

5.19 [expr.const] 的哪一部分说这不是一个常量表达式?

【问题讨论】:

  • 昨天的问题好像是这样的:stackoverflow.com/questions/18878427
  • 它是部分相关的,但不是骗子。事实上,我是在研究完那个问题后才问这个问题的。我认为答案可能是错误的 - 但这里的区别在于 x[0] 具有整数类型,因此应该允许左值到右值的转换,但它仍然不是。
  • 伙计们,这不是骗子!答案不适用于这里!这看起来像是编译器错误或标准缺陷。
  • 撤回了欺骗投票。 (好在你现在可以做到!)
  • 我不知道标准的哪一部分支持它,但总的来说,constexpr 一直被要求使用常量数组元素作为编译时常量。您不能将常量数组元素用作case 或模板参数,除非您使用constexpr,我相信这被认为是正常的。也许您找到了规范错误指定此行为的地方?

标签: c++ c++11 constants constexpr


【解决方案1】:

在 5.19 中:

一个 [...] 表达式是一个常量表达式,除非它涉及以下 [...] 之一:

  • 左值到右值的转换 (4.1),除非它应用于

    • 整数或枚举类型的左值,它引用具有先前初始化、使用常量表达式初始化的非易失性 const 对象,或
    • 一个字面量类型的左值,它引用一个用 constexpr 定义的非易失性对象,或者引用 到此类对象的子对象,或
    • 字面量类型的左值,它指的是用常量初始化的非易失性临时对象 表达

说白了,左值到右值的转换只能在常量表达式中进行,如果:

  • 用常量初始化的常量整数(或枚举)声明:const int x = 3;
  • 声明constexpr:constexpr int x[] = {1,2,3};
  • 使用常量表达式初始化的临时对象...

您的示例确实包括左值到右值的转换,但没有这些例外,因此x 不是常量表达式。但是,如果您将其更改为:

constexpr int x[] = {1,2,3};

static_assert(x[0] == 1, "yay");

int main() {}

那么一切都很好。

【讨论】:

  • 第一个要点如何不适用?在const int x[] = {1,2,3} 中,x[0] 对象是用1 初始化的,不是吗?
  • @user1131467:不,x[0] 未初始化为任何内容。 x 被初始化为 {1,2,3},所以 x[0] 最终的值为 1
  • 我不认为这是正确的。 x[0] 对象不是未初始化的,它是用纯右值1 初始化的。我会检查有关数组初始化的规则。
  • 8.5.1p2 "当 [array] 由初始化列表初始化时,初始化列表的元素将作为 [array] 成员的初始化,按递增的下标或成员顺序。"所以在const int x[] = {1,2,3} 中,1 是对象x[0] 的初始化器——抱歉,x[0] 显然已经初始化了。
  • @user1131467:仔细检查第二个项目符号说明为什么第一个项目不适用...x[0] 显然是一个子对象,而不是一个完整的对象和子对象在项目符号 #2 中允许,但在项目符号 #1 中不允许。
【解决方案2】:

按照标准的当前措辞,这是一个编译器错误,并且程序格式正确。现在正在考虑是否应该成为标准缺陷,因为它很难实施。

详细解释见:

https://groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-discussion/Nv5_2dCHm6M

报告复制如下:

目前 C++11 官方通向 N3690 的措辞包括在内 有以下内容:

条件表达式 e 是一个核心常量表达式,除非 e 的求值将求值以下表达式之一:

  • 左值到右值的转换 (4.1),除非它应用于
    • 一个整数或枚举类型的非易失性左值,它引用一个具有先前初始化的非易失性 const 对象, 用常量表达式初始化

以下声明在全局范围内:

const int x[2] = {42, 43};

定义一个由 2 个 const int 对象组成的数组,使用 {42, 43} 进行列表初始化

在 8.5.1 [dcl.init.aggr]/2 中:

当聚合被初始化列表初始化时,如 8.5.4 中所指定,初始化列表的元素被视为 聚合成员的初始化器,增加下标 或会员订单。

所以第一个元素对象的初始化器是42,而 第二个元素对象的初始化器是43

表达式*x 是一个左值和一个核心常量表达式。它 需要数组到指针的转换和间接 - 两者都不是 其中取消表达式作为核心常量表达式的资格。这 glvalue 表达式引用x 的第一个元素对象。 *x 是一个 整数类型 (const int) 的非易失性左值,它指的是 具有先前初始化的非易失性 const 对象,以及 用常量表达式42初始化。

因此,左值到右值的转换应用于左值*x 在常量表达式中是允许的,所以下面是 格式正确:

constexpr int y = *x;

gcc 和 clang trunk 目前都不接受这个常量 表达,尽管它是按照标准格式良好的。

这是故意的吗?

完整的演示程序:

const int x[2] = {42, 43};
constexpr int y = *x;
int main() {}

实现同样失败,等效左值 x[0] 为 好吧。

【讨论】:

  • 如果你不只是链接到那个解释而且把它放在这里,我会给你一个赞成票;)
  • 你引用它好像它是一个可靠的来源......它不是。
  • @AndrewTomazos:您得到的唯一确认是我的解释是正确的,但当前的措辞应该改进。明确地说“完整的对象”无疑比在并行构造中依赖对比要好。
  • @Andrew:如果我写“着装规定允许穿蓝色和海军蓝长袖衬衫。周五可以穿蓝色短袖衬衫。”我们会得出结论,不允许穿短袖海军衬衫。蓝色通常包括海军色调,但由于平行对比结构,很明显在这种用法中蓝色和海军是相互排斥的。你不同意吗?这种情况有何不同?它们的意思是完整对象,编译器编写者将规则实现为完整对象。是的,为了清楚起见,应该更新措辞。
  • @Andrew:字符串文字并不是那么简单。完整的对象不能重叠,而字符串文字可以(例如 ("recent" + 2) == "cent" 是完全可能的)。这表明字符串文字实际上是字符对象的序列,每个字符对象都是一个完整的对象,而不是一个子对象。
猜你喜欢
  • 2020-10-02
  • 1970-01-01
  • 2021-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多