这里的问题是 C++03 标准中的错误/错误功能/漏洞,不同的编译器试图以不同的方式修补问题。(这个问题在 C++11 标准中不再存在。)
两个标准的第 8.5.3/5 节指定了如何初始化引用。这是 C++03 版本(列表编号是我的):
对 type 的引用cv1 T1
由 type 的表达式初始化,cv2 T2
如下所示:
如果初始化表达式
- 是左值(但不是位域),并且“cv1 T1”与“cv2 T2”引用兼容,或者
- 具有类类型(即
T2
是类类型)并且可以隐式转换为 type 的左值cv3 T3
,其中cv1 T1
引用兼容cv3 T3
然后在第一种情况下引用直接绑定到初始化表达式左值,在第二种情况下引用绑定到转换的左值结果。
否则,引用应为非易失性 const 类型(即,cv1
应为const
)。
如果初始化表达式是右值,具有T2
类类型,并且与cv1 T1
引用兼容cv2 T2
,则引用以下列方式之一绑定(选择是实现定义的):
- 引用绑定到由右值表示的对象(参见 3.10)或该对象中的子对象。
- 创建了一个 [sic]类型
cv1 T2
的临时对象,并调用构造函数将整个右值对象复制到临时对象中。引用绑定到临时对象或临时对象中的子对象。
无论复制是否实际完成,用于制作复制的构造函数都应该是可调用的。
cv1 T1
否则,使用非引用复制初始化规则(8.5)从初始化表达式创建并初始化一个临时类型。然后将引用绑定到临时文件。
手头的问题涉及三种类型:
- 要创建的引用的类型。标准(两个版本)将此类型表示为
T1
. 在这种情况下,它是struct A
。
- 初始化表达式的类型。标准将此类型表示为
T2
. 在这种情况下,初始化表达式是变量c
,所以T2
也是struct C
。请注意,由于与struct A
不引用兼容struct C
,因此无法直接将引用绑定到c
. 需要一个中间人。
- 中间体的类型。标准将此类型表示为
T3
. 在这种情况下,这是struct B
。请注意,应用转换运算符会将左值转换C::operator B()
为右值。c
c
我标记为 1.1 和 3 的初始化已经失效,struct A
因为struct C
. C::operator B()
需要使用转换运算符。1.2 out 因为这个转换运算符返回一个右值,所以这个规则 1.2 out。剩下的就是选项 4,创建一个临时类型的cv1 T1
. 严格遵守 2003 版标准强制为这个问题创建两个临时的,即使只有一个就足够了。
2011 版标准通过将选项 3 替换为
似乎 gcc 系列编译器选择了严格的合规性而不是意图(避免创建不必要的临时变量),而其他打印“b”的编译器选择了对标准的意图/更正。选择严格合规不一定值得称道。在 2003 版标准(例如 )中还有其他错误/错误功能,std::set
其中 gcc 家族选择理智而不是严格遵守。