我有两个类A
和B
,每个类定义转换为B
。 A
有一个转化运算符B
,B
有A
的构造函数。对static_cast<B>
的调用不应该含糊不清吗?使用g ++,这段代码编译并选择转换构造函数。
#include<iostream>
using namespace std;
struct B;
struct A {
A(const int& n) : x(n) {}
operator B() const; //this const doesn't change the output of this code
int x;
};
struct B{
B(const double& n) : x(n) {}
B(const A& a);
double x;
};
A::operator B() const //this const doesn't change the output of this code
{
cout << "called A's conversion operator" << endl;
return B(double(x));
}
B::B(const A& a)
{
cout << "called B's conversion constructor" << endl;
x = (double) a.x;
}
int main() {
A a(10);
static_cast<B>(a); // prints B's conversion constructor
}
答案 0 :(得分:13)
对于用户定义的转换序列;转换构造函数和转换运算符之间似乎没有给出优先级,它们都是候选者;
§13.3.3.1.2/ 1 用户定义的转换序列
用户定义的转换序列包括初始标准转换序列,然后是用户定义的转换(12.3),后跟第二个标准转换序列。如果用户定义的转换由构造函数(12.3.1)指定,则初始标准转换序列将源类型转换为构造函数的参数所需的类型。如果转换函数(12.3.2)指定了用户定义的转换,则初始标准转换序列会将源类型转换为转换函数的隐式对象参数。
因此,如果转换是;
B b2 = a; // ambiguous?
它可能含糊不清,编译失败。 Clang无法编译,g ++接受代码并使用构造函数; demo code,VS也接受代码。 VS和g ++调用转换构造函数(根据OP代码)。
考虑到发布的代码,需要考虑用户定义的转换序列(通过构造函数和转换运算符)以及static_cast
的使用。
§5.2.9/ 4 静态演员
如果声明
T
格式正确,则表达式e可以使用static_cast
形式的static_cast<T>(e)
显式转换为T t(e);
类型,对于某些发明的临时表达式变量t
(8.5)。这种显式转换的效果与执行声明和初始化,然后使用临时变量作为转换结果相同。当且仅当初始化将其用作左值时,表达式e
才用作glvalue。
从上面的引用中,static_cast
等同于B temp(a);
,因此,使用了直接初始化序列。
§13.3.1.3/ 1 按构造函数初始化
当类类型的对象被直接初始化(8.5),从相同或派生类类型(8.5)或默认初始化(8.5)的表达式进行复制初始化时,重载决策选择构造函数即可。对于直接初始化或默认初始化,候选函数是正在初始化的对象的类的所有构造函数。对于复制初始化,候选函数是该类的所有转换构造函数(12.3.1)。参数列表是初始化的表达式列表或赋值表达式。
一般情况下(排除标记为explicit
和const
关注的任何构造函数和运算符),给定B(const A& a);
构造函数并构造B
来自A
构造函数应该获胜,因为它在考虑best viable function时提供完全匹配;因为不需要进一步implicit conversions(§13.3;重载分辨率)。
如果删除了构造函数B(const A& a);
,则转换(使用static_cast<>
仍然会成功,因为用户定义的转换运算符是候选者,并且其使用不明确。
§13.3.1.4/ 1 按用户定义的转换对类进行复制初始化
在8.5中指定的条件下,作为类类型对象的复制初始化的一部分,可以调用用户定义的转换以将初始化表达式转换为要初始化的对象的类型。
引用来自N4567 C ++标准草案。
在对象构造之外调用用户定义的转换序列也是有益的,即调用方法。
鉴于代码清单(以及上述规则);
#include <iostream>
using namespace std;
struct A;
struct B {
B() {}
B(const A&) { cout << "called B's conversion constructor" << endl; }
};
struct A {
A() {}
operator B() const { cout << "called A's conversion operator" << endl; return B(); }
};
void func(B) {}
int main() {
A a;
B b1 = static_cast<B>(a); // 1. cast
B b2 = a; // 2. copy initialise
B b3 ( a ); // 3. direct initialise
func(a); // 4. user defined conversion
}
Clang,g ++(demo)和VS提供不同的结果,因此可能有不同的合规水平。
根据上述规则,1到3.都应该成功,因为B
转换构造函数是候选者,不需要进一步的用户转换;这些表格使用直接构造和复制初始化。阅读标准(上述摘录,特别是§13.3.3.1.2/ 1和§13.3.1.4/ 1,然后是§8.5/ 17.6.2),2。和4.可能/应该失败并且含糊不清 - 因为正在考虑转换构造函数和转换运算符,没有明确的顺序。
我认为这可能是一个非预期的用例(类型能够以这种方式相互转换;有一个论点,即一个转换序列将是一般用例)。