列表初始化参考:GCC或Clang是否正确?

Mik*_*Lui 11 c++ language-lawyer list-initialization

鉴于这个例子:

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
}
Run Code Online (Sandbox Code Playgroud)

错误如评论中所述.
使用gcc 8.2.1clang 7.0.1,并且不同意本例中发生的情况.有人可以澄清一下吗?

在列表初始化中:

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

否则,如果T是引用类型,则生成由T引用的类型的prvalue.prvalue通过copy-list-initialization或direct-list-initialization初始化其结果对象,具体取决于引用的初始化类型.然后使用prvalue来直接初始化参考.[注意:像往常一样,如果引用类型是非const类型的左值引用,则绑定将失败并且程序格式错误. - 结束说明]

在参考初始化中:

给定类型"cv1 T1"和"cv2 T2",如果T1与T2的类型相同,则"cv1 T1"与"cv2 T2"相关,或者T1是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"类型的右值或函数左值,其中"cv1 T1"与"cv3 T3"参考兼容
...然后在第二种情况下转换结果的值称为转换初始化器.如果转换的初始化程序是prvalue,则将其类型T4调整为键入"cv1 T4"

...

否则:
- 如果T1或T2是类类型且T1与T2无参考相关,则使用用户定义的转换使用"cv1 T1"类型的对象的复制初始化规则来考虑用户定义的转换. .如非参考拷贝初始化所述,调用转换函数的结果然后用于直接初始化引用.对于此直接初始化,不考虑用户定义的转换.

...

否则,初始化表达式被隐式转换为类型为"cv1 T1"的prvalue.应用临时实现转换,并将引用绑定到结果.

这些规则非常细微,我无法完全掌握每种情况.对我来说,似乎应该生成一个prvalue(我同意clang),但是参考初始化的语言和与列表初始化的交互非常模糊.

Bri*_*ian 10

让我们按照正确的顺序阅读标准,以便我们知道哪些部分适用于手头的情况.

[dcl.init]/17说:

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

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

对象或类型引用的列表初始化T定义如下:( ...不适用的情况从此引用中删除...)否则,如果初始化列表具有单个元素类型ET不是引用类型或其引用类型与引用相关E...否则,如果T是引用类型,T则生成引用类型的prvalue .prvalue通过copy-list-initialization或direct-list-initialization初始化其结果对象,具体取决于引用的初始化类型.然后使用prvalue来直接初始化参考.[ 注意:像往常一样,如果引用类型是非const类型的左值引用,则绑定将失败并且程序格式错误.- 尾注 ]

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

给定类型的" CV1 T1 "和" CV2 T2 "," CV1 T1 "是参考相关于" CV2 T2 "如果T1是相同的类型T2,或者T1是一个基类的T2.

因此,在您的代码中,引用的类型int与初始化列表中的类型不是引用相关的,即S.因此,通过[dcl.init.list]/3,int生成一个类型的prvalue ,它采用该形式int{s}.正如笔记所说,在这种情况下iref2,程序是不正确的,因为它试图将非const左值引用绑定到prvalue.在这种情况下iref3,程序应该编译,因为iref3它被绑定到prvalue结果int{s}.