【问题标题】:List initialization of a reference: is GCC or Clang correct?列出引用的初始化:GCC 或 Clang 是否正确?
【发布时间】:2019-01-09 23:25:48
【问题描述】:

举个例子:

int g_i = 10;
struct S {
    operator int&(){ return g_i; }
};

int main() {
    S s;
    int& iref1 = s; // implicit conversion

    int& iref2 = {s}; // clang++ error, g++ compiles fine:
                      // `s` is converted
                      // to a temporary int and binds with
                      // lvalue reference

    int&& iref3 = {s}; // clang++ compiles, g++ error:
                       // cannot bind rvalue reference
                       // to lvalue
}

错误如 cmets 中所述。
gcc 8.2.1clang 7.0.1 被使用,并且不同意此示例中发生的情况。有人可以澄清一下吗?

In list initialization :

否则,如果初始化列表有一个类型为 E 的元素,并且 T 不是引用类型或 它的引用类型与 E 引用相关,则对象或引用从该元素初始化(通过复制初始化复制列表初始化,或通过直接初始化直接列表初始化);如果需要缩小转换(见下文)将元素转换为 T,则程序格式错误。

否则,如果 T 是引用类型,则生成 T 引用的类型的纯右值。 纯右值通过复制列表初始化或直接列表初始化来初始化其结果对象,具体取决于关于初始化的那种,供参考。然后使用纯右值直接初始化引用。 [ 注意:与往常一样,如果引用类型是对非 const 类型的左值引用,则绑定将失败并且程序格式错误。 — 尾注 ]

In reference initialization:

给定类型“cv1 T1”和“cv2 T2”,如果 T1 与 T2 的类型相同,或者 T1 是 T2 的基类,则“cv1 T1”与“cv2 T2”引用相关强>。如果
“cv1 T1”与“cv2 T2”引用兼容 - T1 与 T2 相关,或
- T2 为“noexcept 函数”,T1 为“函数”,其他函数类型相同,

...and later on there's some (personally ambiguous) language on user-defined conversions:

例如:

如果引用是左值引用和初始化表达式
...
有一个类类型(即T2是一个类类型),其中T1与T2没有引用相关,可以转换为“cv3 T3”类型的左值,其中“cv1 T1”与“cv3 T3”引用兼容(通过枚举适用的转换函数([over.match.ref])并通过重载决议选择最佳转换函数来选择),
...
那么引用就绑定到...值转换的结果

...

否则,如果初始化表达式
...
具有类类型(即 T2 是类类型),其中 T1 与 T2 不引用相关,并且可以转换为“cv3 T3”类型的右值或函数左值,其中“cv1 T1”是引用兼容的使用“cv3 T3”
... 那么在第二种情况下转换的 ... 结果的值称为转换后的初始值设定项。如果转换后的初始化器是纯右值,则其类型 T4 调整为类型“cv1 T4”

...

否则:
- 如果 T1 或 T2 是类类型,并且 T1 与 T2 没有引用相关,用户定义的转换被认为是使用通过用户定义的转换复制初始化类型为“cv1 T1”的对象的规则 ... 调用转换函数的结果,如非引用复制初始化所述,然后用于直接初始化引用。 对于这种直接初始化,不考虑用户定义的转换

...

否则,初始化表达式被隐式转换为“cv1 T1”类型的纯右值。应用临时实现转换并将引用绑定到结果。

这些规则非常微妙,我无法完全掌握每种情况。 对我来说,似乎应该生成一个纯右值(我同意 clang),但是引用初始化的语言以及与列表初始化的交互非常模糊。

【问题讨论】:

    标签: c++ language-lawyer list-initialization


    【解决方案1】:

    让我们以正确的顺序阅读标准,以便我们知道哪些部分适用于当前的情况。

    [dcl.init]/17 说:

    初始化器的语义如下...如果初始化器是(非括号)braced-init-list或者是=braced-init-list em>,对象或引用是列表初始化的 (11.6.4) ...

    所以我们转到 [dcl.init.list] (11.6.4)。第 3 段说:

    T 类型的对象或引用的列表初始化定义如下:(...不适用的情况从此引用中省略...)否则,如果初始化列表具有单个元素类型为E 并且T 不是引用类型或其引用类型与E 引用相关...否则,如果T 是引用类型,则为@987654327 引用的类型的纯右值生成@。 prvalue 根据引用的初始化类型,通过复制列表初始化或直接列表初始化来初始化其结果对象。然后使用纯右值直接初始化引用。 [ 注意: 如 通常,如果引用类型是对 非常量类型。 ——尾注 ]

    根据[dcl.init.ref]/4:

    给定类型“cv1T1”和“cv2T2”,“cv1T1”是如果T1T2 的类型相同,或者T1T2 的基类,则与“cv2 T2”的引用相关

    因此,在您的代码中,引用的类型int 与初始化列表中的类型(即S)没有引用相关。因此,通过 [dcl.init.list]/3 生成了一个int 类型的纯右值,它采用int{s} 的形式。正如注释所说,在iref2 的情况下,程序格式错误,因为它试图将非常量左值引用绑定到纯右值。在iref3 的情况下,程序应该编译,因为iref3 被绑定到prvalue 结果int{s}

    【讨论】:

      猜你喜欢
      • 2018-07-24
      • 1970-01-01
      • 2021-09-10
      • 1970-01-01
      • 1970-01-01
      • 2018-11-29
      • 1970-01-01
      • 2016-12-10
      • 1970-01-01
      相关资源
      最近更新 更多