为什么 GCC 以不同方式处理这些片段?

Why are these snippets handled differently by GCC?

第一个片段编译时没有任何警告(live example):

#include <iostream>

struct A {
  constexpr A(): i(5){}
  constexpr operator int() { return 5; }
  int i;
};

int main() {
    A a;
    int b[a]{ 0, 1, 2, 3, 4 };
    std::cout << b[4] << '\n';
}

现在通过在转换运算符 (live example) 中返回 i 来更改上述代码段:

constexpr operator int() { return i; }

GCC 警告说 b 是一个 VLA。

对我来说,这两种变体似乎都符合 C++14 中的第 §5.19 [expr.const]/3 段。

数组大小必须是编译时常量,但在第二个示例中,A::i 的初始化直到 运行-time 才会发生。

您正在 i 上执行 l-t-r 转换,但为了不违反此处的 [expr.const]/(2.7),(2.7.3) 必须适用:

(2.7.1) 关注完整的对象,(2.7.2) 讨论字符串文字,而 (2.7.4) 关注其生命周期开始于表达式求值的对象——不适用,因为 a 的声明先于 b 的声明。

a 定义为 constexpr 并且代码是兼容的。


一个小附录来阐明标准的内容:括号内的表达式必须是 std::size_t 类型的转换常量表达式([dcl.array]/1), which is defined in [expr.const]/4 as

A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and […requirements that are met…]

因此,实际上,标准对

是否感兴趣
constexpr std::size_t s = a; 

有效。由于上述原因,它不是 - 尝试使用先前定义的非 constexpr 对象的子对象。