引用的列表初始化:GCC或Clang正确吗?

11
给定以下示例:
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
}

错误已经在注释中描述过了。
使用了gcc 8.2.1clang 7.0.1,两者对此示例的操作存在分歧。能否请有人澄清一下情况?

在列表初始化中:

否则,如果初始化程序列表只包含一个类型为E的元素,并且T不是引用类型或其引用类型与E相关联,则该对象或引用从该元素处进行初始化(对于拷贝列表初始化采用拷贝初始化,对于直接列表初始化采用直接初始化);如果需要将元素转换为T,则程序发生错误。

否则,如果T是引用类型,则生成所引用类型的prvalue。 prvalue通过拷贝列表初始化或直接列表初始化来初始化其结果对象,这取决于引用的初始化方式。然后使用prvalue来直接初始化引用。[注意:通常情况下,如果引用类型是非const类型的左值引用,则绑定将失败,程序将不合法。— end note]

在引用初始化中:

给定类型“cv1 T1”和“cv2 T2”,如果T1与T2相同,或者T1是T2的基类,则“cv1 T1”与“cv2 T2”相关联。“cv1 T1”与“cv2 T2”引用兼容,如果
- T1与T2关联引用,或者
- T2为“noexcept function”,T1为“function”,其中函数类型是其它情况下相同的,

...稍后出现了一些(个人模棱两可的)关于用户定义转换的语言:

例如:

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

...

否则,如果初始化表达式
...
具有类类型 (即 T2 是类类型),其中 T1 与 T2 不相关联,并且可以转换为类型“cv3 T3”的 rvalue 或函数 lvalue,其中“cv1 T1”与“cv3 T3”兼容
... 那么第二种情况下的转换结果的值称为转换后的初始化程序。如果转换后的初始化程序是 prvalue,则将其类型 T4 调整为类型“cv1 T4”
否则:
- 如果 T1 或 T2 是类类型,并且 T1 与 T2 不相关联,则使用对象类型“cv1 T1”的复制初始化的用户定义转换考虑规则...根据非引用复制初始化的描述,调用转换函数的结果随后用于直接初始化引用。 对于此直接初始化,不考虑用户定义的转换。
否则,将隐式将初始化表达式转换为类型为“cv1 T1”的 prvalue。 进行暂时材料化转换,然后将引用绑定到结果。
这些规则非常微妙,我无法完全掌握每种情况。对我来说,似乎应该生成 prvalue(我同意 clang 的观点),但关于引用初始化和列表初始化的交互的语言非常模糊。
1个回答

10
让我们按正确的顺序阅读标准,以便了解哪些部分适用于手头的情况。
[dcl.init]/17说:
初始化程序的语义如下...如果初始化程序是一个(非括号)花括号初始化列表或者是“=花括号初始化列表”,则对象或引用将进行列表初始化(11.6.4)...
因此,我们进入[dcl.init.list](11.6.4)。第3段说:
类型为T的对象或引用的列表初始化定义如下:(...不适用的情况已从此引语中省略...)否则,如果初始化程序列表具有类型为E的单个元素,并且T不是引用类型或其引用类型与E相关,则生成类型为E的prvalue...否则,如果T是引用类型,则生成类型由T引用的prvalue。prvalue通过复制列表初始化或直接列表初始化对其结果对象进行初始化,具体取决于引用的初始化方式。然后使用prvalue对引用进行直接初始化。[注意:如果引用类型是非const类型的lvalue引用,则像往常一样,绑定将失败,程序将无效。-结束注释]
根据[dcl.init.ref]/4:
给定类型“cv1 T1”和“cv2 T2”,如果T1与T2相同或T1是T2的基类,则“cv1 T1”与“cv2 T2”是相关的。
因此,在您的代码中,引用类型int与初始化程序列表中的类型S不相关。因此,根据[dcl.init.list]/3,将生成类型为int的prvalue,并且它采用int{s}的形式。正如注释所说,在iref2的情况下,程序无效,因为它试图将非const lvalue引用绑定到prvalue。在iref3的情况下,程序应该编译,因为iref3正在绑定到prvalue结果int{s}。

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