在继续阅读本文之前,请阅读C++ 中复制初始化和直接初始化有区别吗?首先,确保你理解它在说什么。
我先在这里总结一下规则(阅读标准n3225 8.5/16、13.3.1.3、13.3.1.4和13.3.1.5),
1) 对于直接初始化,所有的构造函数都将被视为重载集,重载解析将根据重载解析规则选择最佳的一个。
2) 对于复制初始化,源类型与目的类型相同或派生自目的类型,规则同上,只是只有转换构造函数(无显式构造函数)才会被视为重载集。这实际上意味着显式复制/移动构造函数不会被考虑到重载集中。
3)对于上面(2)中没有包括的复制初始化情况(源类型与目的类型不同,不是从目的类型派生的),我们首先考虑用户定义的可以从源类型转换为目的类型的转换序列或(当使用转换函数时)到其派生类。如果转换成功,则使用结果直接初始化目标对象。
3.1) 在此用户定义的转换序列期间,将根据 8.5/16 和 13.3.1.4 中的规则考虑转换 ctor(非显式 ctor)和非显式转换函数。
3.2)结果纯右值将直接初始化目标对象,如(1)中列出的规则,见8.5/16。
好吧,规则说得够多了,让我们看看一些奇怪的代码,我真的不知道我的推理哪里错了,或者只是所有的编译器都错了。请帮助我,谢谢。
struct A
{
A (int) { }
A() { }
explicit A(const A&) { }
};
struct B
{
operator A() { return 2; }
//1) visual c++ and clang passes this
//gcc 4.4.3 denies this, says no viable constructor available
};
int main()
{
B b;
A a = b;
//2) oops, all compilers deny this
}
据我了解,对于(1),
operator A() { return 2; }
因为C++有一个规则,函数返回是作为复制初始化的,根据上面的规则,2会先隐式转换为A,应该没问题,因为A有一个构造函数A(int)。然后转换后的临时prvalue将用于直接初始化返回的对象,这也应该没问题,因为直接初始化可以使用显式复制构造函数。所以 GCC 是错误的。
对于 (2),
A a = b;
在我的理解中,首先b被操作符A()隐式转换为A,然后转换后的值将用于直接初始化a,当然可以调用显式复制构造函数?因此这应该通过编译并且所有编译器都是错误的?
请注意,对于 (2),visual c++ 和 clang 都有类似于“错误,无法从 B 转换为 A”的错误,但如果我删除 A 的复制构造函数中的显式关键字,错误就消失了。
谢谢阅读。
编辑 1
因为还是有人没明白我的意思,所以我引用了 8.5/16 中的以下标准,
否则(即,对于剩余的复制初始化情况),可以从源类型转换到目标类型或(当使用转换函数时)到其派生类的用户定义转换序列被枚举,如 13.3 中所述。 1.4,最好的一个是通过重载决议(13.3)选择的。如果转换无法完成或不明确,则初始化格式错误。以初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用初始化目标类型的 cv 非限定版本的临时版本。临时是prvalue。然后根据上面的规则,调用的结果(这是构造函数案例的临时结果)用于直接初始化,作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;见 12.2、12.8。
请注意,它确实提到了用户定义转换后的直接初始化。这意味着,据我了解,以下代码应遵守我评论的规则,clang、coomeau online、visual c++ 都证实了这一点,但 GCC 4.4.3 (1)和(2)都失败了。虽然这是一个奇怪的规则,但它遵循标准的推理。
struct A
{
A (int) { }
A() { }
explicit A(const A&) { }
};
int main()
{
A a = 2; //1)OK, first convert, then direct-initialize
A a = (A)2; //2)oops, constructor explicit, not viable here!
}