请参见以下代码:
struct X;
struct Y {
Y() {}
Y(X&) = delete;
};
struct X {
X() {}
operator Y() {
return{};
}
};
int main() {
X x;
static_cast<Y>(x);
}
在这里,Y
的构造函数采用X
被显式删除,而X
的转换运算符转换为Y
。在这两个直接矛盾的方面,似乎=delete
总是赢;我在GCC,Clang和VC ++的某些最新版本上进行了测试。
问题:这是“正确”的行为吗?我认为转换构造函数和转换运算符之间没有特殊的优先级,因此上面的代码应产生重载解析度歧义错误。但事实并非如此。它抱怨删除功能的使用。是因为可以保证复制的删节率吗?
我用Google搜索并找到了Conversion constructor vs. conversion operator: precedence。在该问题中,选择了转换运算符是因为由于转换构造函数中存在const
,因此更匹配。但是,就我而言,将Y(X&)
替换为Y(X const&)
并没有任何改变。
实际上,我想遇到的情况如下:
X x;
Y y1(x); // Error
Y y2 = static_cast<Y>(x); // OK
是的,这可能很愚蠢,但是确实有一些内置类型的行为就像这样:替换X <- int&
,Y <- int&&
。无法创建完全模仿内置引用类型的用户定义类型似乎是在当前C ++中迫切需要的部分...
答案 0 :(得分:2)
根据标准11.6.17.6.2
如果初始化是直接初始化,或者如果是复制初始化,则 源类型的cv非限定版本与该类的类相同,或者是该类的派生类 目的地,考虑构造函数。
然后标准告诉我们(11.6.16
)
以形式[...]以及
new
表达式(8.5.2.4),static_cast
表达式(8.5.1.9),功能符号类型转换(8.5。 1.3),mem初始化程序(15.6.2)和条件的括号初始列表形式称为直接初始化。
您的示例通过static_cast
初始化了一个临时文件,因此由于直接初始化,编译器只允许使用构造函数,因此会出现错误。
答案 1 :(得分:2)
问题:这是“正确”的行为吗?我认为转换构造函数和转换运算符[...]
之间没有特别的优先级
那不是很正确。您正在按原样查看代码:
struct Y {
Y() {}
Y(X&) = delete;
};
但是实际上还有更多的东西。对于编译器,Y
如下:
struct Y {
Y() {}
Y(X&) = delete;
Y(Y&&) = default;
Y(Y const&) = default;
};
此处的选择不在Y(X&)
和X::operator Y()
之间。选择主要在Y(X&)
和Y(Y&&)
之间。而且前者比后者更好(无论如何,正如您在问题中提到的,它是X&
或X const&
作为参数)。但是它已被删除,因此转换格式错误。
如果我们要进行复制初始化而不是直接初始化:
Y y = x;
然后都是一样可行的(因此模棱两可)。是的,您真的希望这个模棱两可。 = delete
不会从候选集中删除!
将构造函数从Y(X&)
更改为Y(X const&)
将具有首选的转换功能。
是的,这可能很愚蠢,但实际上有一些内置类型的行为就像这样:替代
X <- int&
,Y <- int&&
是,但是在原始示例中,X
和Y
是不同的类型。在这里,它们代表相同类型的不同值类别。这个新示例起作用或不起作用的原因完全不同:
X x;
Y y1(x); // Error
Y y2 = static_cast<Y>(x); // OK
是真的:
int& x = ...;
int&& y(x); // error, can't bind rvalue reference to lvalue
int&& y2 = static_cast<int&&>(x); // ok. this is exactly std::move(x)
与引用兼容类型的引用绑定与转换优先级的问题不同。