【问题标题】:Why are these snippets handled differently by GCC?为什么 GCC 对这些片段的处理方式不同?
【发布时间】:2016-03-27 15:02:19
【问题描述】:

第一个 sn-p 编译时没有任何警告 (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 来更改上述 sn-p:

constexpr operator int() { return i; }

GCC 警告 b 是一个 VLA。

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

【问题讨论】:

    标签: c++ gcc g++ language-lawyer c++14


    【解决方案1】:

    您正在对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)类型的转换常量表达式,在[expr.const]/4中定义为

    类型转换的常量表达式 T 是一个表达式,隐式转换为T 类型,其中转换后的 expression 是一个常量表达式,并且 […满足的要求…]

    因此,实际上,标准是否对

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

    将是有效的。由于上述原因,事实并非如此 - 尝试使用先前定义的非constexpr 对象的子对象。

    【讨论】:

    • 为什么a 在这种情况下必须进行左值到右值的转换?我在问这个问题,因为 §5.19/3 (N4140) 中 converted constant expression 的定义似乎不需要从声明 int b[a]{ 0, 1, 2, 3, 4 }; 中的 a 进行这种转换。
    • @Ayrosa 好吧,a 永远不必经历一个,但必须调用它的转换运算符才能将其转换为 std::size_t。 (a 是括号内std::size_t 类型的转换后的常量表达式)
    • 我花了一段时间才真正掌握您所说的内容并在您的答案中突出显示。对一个人来说,完全理解标准绝对不是一件容易的事。很好的答案 (+1)。
    • @Ayrosa id-expression a 具有类类型 A,但由于数组的边界必须由 std::size_t 类型的转换常量表达式指定,实现尝试找到隐式转换序列将其转换为目标类型。参见 [conv] 4\3, 6。它找到用户定义的转换函数constexpr operator int (),然后将其用于初始化std::size_t 类型的临时值,该值将用作数组的边界。但是!
    • @Ayrosa 但是!生成的完整表达式不是核心常量表达式,因为在初始化期间,隐含的对象参数 a(参见 [over.match.conv] 13.3.1.5\2)被用作一个左值(没有 ltr 转换),您定义的转换函数为 a 的子对象评估 ltr,这对于核心常量表达式是不可接受的,因为 a 的生命周期都没有开始在考虑的完整表达式的评估中,也不等......
    【解决方案2】:

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

    【讨论】:

    • @YSC 可以,但是main函数中的变量a不是。
    • this 才是真正的警告源,不是吗?
    猜你喜欢
    • 2020-10-07
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    • 2019-08-18
    • 2021-11-01
    • 1970-01-01
    • 2020-10-03
    • 2020-05-17
    相关资源
    最近更新 更多