在这里阅读关于转换运算符和构造函数的一些问题让我思考它们之间的相互作用,即当存在"模糊"调用时.请考虑以下代码:
class A;
class B {
public:
B(){}
B(const A&) //conversion constructor
{
cout << "called B's conversion constructor" << endl;
}
};
class A {
public:
operator B() //conversion operator
{
cout << "called A's conversion operator" << endl;
return B();
}
};
int main()
{
B b = A(); //what should be called here? apparently, A::operator B()
return 0;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码显示"被称为A的转换运算符",这意味着调用转换运算符而不是构造函数.如果operator B()从中删除/注释掉代码A,编译器将很乐意切换到使用构造函数(不对代码进行其他更改).
我的问题是:
B b = A();是一个模糊的调用,因此这里必须有某种类型的优先级.这个优先权究竟在哪里确定?(将赞赏C++标准的参考/引用)A对象应该如何成为一个B对象, …c++ constructor operators type-conversion conversion-operator
clang和gcc都接受以下代码并选择A::operator B*.
struct B
{
};
struct A : B
{
operator A*();
operator B*();
};
A a;
void* x = a;
Run Code Online (Sandbox Code Playgroud)
我对标准的阅读 - 特别是下面用粗体突出显示的句子 - 表明这种转换应该是模棱两可的.
这两个A::operator A*和A::operator B*是重载,因为候选人A*和B*都转换为void*通过一个标准的转换.因为隐含的对象参数A&是唯一的参数,所以只考虑从隐含的对象参数转换为隐含的对象参数的转换序列 - 忽略转换函数产生的类型.在这两种情况下,隐含的对象参数都是初始化表达式的类型A,而隐含的对象参数是A&.如果两个转换序列相同,则无法区分两个候选者.
8.5初始值设定项[dcl.init]
初始化器的语义如下.的目标类型是对象或引用的类型被初始化和源类型是初始化表达式的类型.
- 如果目标类型是[ reference/array/class ...] [已删除的详细信息不适用于此方案]
- 否则,如果源类型是(可能是cv限定的)类类型,则考虑转换函数. 列举了适用的转换函数(13.3.1.5),并通过重载决策(13.3)选择最佳函数.调用如此选择的用户定义转换以将初始化表达式转换为正在初始化的对象.如果转换不能完成或不明确,则初始化是错误的.
13.3.1.5通过转换函数初始化[over.match.conv]
在8.5中指定的条件下,作为非类型对象初始化的一部分,可以调用转换函数将类类型的初始化表达式转换为要初始化的对象的类型.重载分辨率用于选择要调用的转换函数.假设"cv1 T"是要初始化的对象的类型,并且"cv S"是初始化表达式的类型,其中S是类类型,候选函数选择如下:
- 考虑S及其基类的转换函数.那些未隐藏在S和yield类型T中的非显式转换函数或可通过标准转换序列(13.3.3.1.1)转换为类型T的类型是候选函数.对于直接初始化,那些未隐藏在S和yield类型T中的显式转换函数或者可以通过限定转换(4.4)转换为类型T的类型也是候选函数.返回cv限定类型的转换函数被认为是为此选择候选函数的过程产生该类型的cv非限定版本.返回"引用cv2 X"的转换函数返回lvalues或xvalues,具体取决于引用类型,类型为"cv2 X",因此被认为是为此选择候选函数的过程产生X.
参数列表有一个参数,它是初始化表达式.[注意:此参数将与转换函数的隐式对象参数进行比较. …
以下代码工作正常:
#include <iostream>
struct B
{
operator int()
{
return int();
}
};
struct A
{
A(int, int){ std::cout << "A(int, int)" << std::endl; }
};
A a({B(), B()});
int main()
{
}
Run Code Online (Sandbox Code Playgroud)
并产生输出:
A(int, int)
Run Code Online (Sandbox Code Playgroud)
但我不明白为什么?标准说的是:
但是,当考虑构造函数或用户定义的转换函数的参数时,如果在类复制初始化的第二步中复制/移动临时函数时,通过13.3.1.3调用该函数,则通过13.3.1.7传递时初始化程序列表作为单个参数,或者当初始化程序列表只有一个元素并且转换为某个类X或引用(可能是cv限定的)时,X仅被视为X [...]的构造函数的第一个参数考虑标准转换序列和省略号转换序列
所以在我们的例子中,我们考虑了构造函数的参数(它是{B(), B()}).更确切地说,我们将initializer-list作为单个参数传递(我引用的规则中的第二个案例).现在,我们需要将initializer-list的第一个元素(临时类型B)转换为int,并且唯一的方法是应用用户定义的convertion(B::operator int()).但是,正如在规则末尾所说的那样,我只引用标准转换序列和省略号转换序列.因为,该代码不起作用,它应该抛出A(int, int)不可行或类似的错误.
怎么了.可能是一个错误?
鉴于以下代码(在GCC 4.3中),为什么在这两种情况下都会调用转换为引用?
class A { };
class B {
public:
operator A() {}
operator A&() {}
};
int main() {
B b;
(A) b;
(A&) b;
}
Run Code Online (Sandbox Code Playgroud)