Jue*_*gen 4 c++ gcc operators type-conversion language-lawyer
这是这个问题的后续。我使用了这段代码并使用了 clang trunk 和 gcc trunk:
struct A
{
};
struct T
{
A a;
operator A() { return a; }
template <typename T> operator const T&() = delete;
};
struct C
{
A a;
};
int main()
{
C c;
T t;
c.a = t;
}
Run Code Online (Sandbox Code Playgroud)
Clang 没有什么可抱怨的,但 gcc 有:
<source>: In function 'int main()':
<source>:23:11: error: use of deleted function 'T::operator const T&() [with T = A]'
23 | c.a = t;
| ^
<source>:10:27: note: declared here
10 | template <typename T> operator const T&() = delete;
|
^~~~~~~~
Run Code Online (Sandbox Code Playgroud)
那么,哪个编译器是正确的,哪个编译器是错误的呢?
我想看看 gcc 是错误的,但是我怎样才能克服这个错误呢?
Clang 和 GCC 都是错误的,但至少 GCC 停止了编译,所以它稍微好一些。
\nA是一个类类型,因此分配给它会通过重载operator=。你没有提供一个,所以编译器提供了两个,效果是
A& operator=(A const&) = default;\nA& operator=(A&&) = default;\nRun Code Online (Sandbox Code Playgroud)\n该引用参数是需要从t表达式中初始化的参数c.a = t。对于任何一个operator=,都可以明确地绑定引用。
\n\n[dcl.init.ref]
\n5对类型 \xe2\x80\x9ccv1 T1\xe2\x80\x9d 的引用由 \ntype \xe2\x80\x9ccv2 T2\xe2\x80\x9d 的表达式初始化,如下所示:
\n\n
\n- \n
如果引用是左值引用并且初始化表达式
\n\n
\n- [...]
\n- 具有类类型(即T2是类类型),其中T1与T2没有引用相关,并且可以转换为\n\xe2\x80\x9ccv3 T3\xe2\x80\x9d类型的左值,其中\xe2\x80\x9ccv1 T1\xe2\x80\x9d 与 \xe2\x80\x9ccv3 T3\xe2\x80\x9d 引用兼容(此转换是通过枚举适用的转换函数来选择的([over.match. ref])并通过\n过载分辨率选择最好的一个),
\n那么该引用将...绑定到第二种情况下转换的左值结果(或者在任何一种情况下,绑定到该对象的相应基类子对象)。
\n- \n
否则,如果初始化表达式
\n\n
\n- [...]
\n- 具有类类型(即 T2 是类类型),其中 T1 与 T2 没有引用相关,并且可以转换为类型为 \xe2\x80\x9ccv3 T3\xe2\x80\x9d 的右值或函数\n左值,其中 \xe2\x80\x9ccv1 T1\xe2\x80\x9d 与 \n\xe2\x80\x9ccv3 T3\xe2\x80\x9d 引用兼容(请参阅 [over.match.ref]),
\n那么...第二种情况下的转换结果称为\n转换后的初始值设定项。如果转换后的初始值设定项是纯右值,\n类型 T4 将调整为类型 \xe2\x80\x9ccv1 T4\xe2\x80\x9d ([conv.qual]),并且\n临时物化转换 ([conv.rval]) 为应用。在任何情况下,引用都会绑定到生成的左值(或适当的基类子对象)。
\n
关于为这两种情况建立候选集的主题,该标准说
\n\n\n[over.match.ref]
\n1在 [dcl.init.ref] 中指定的条件下,引用可以直接绑定到将转换函数应用于初始化表达式的结果。重载解析用于选择\n要调用的转换函数。假设 \xe2\x80\x9creference to cv1 T\xe2\x80\x9d\n 是正在初始化的引用的类型,而 \xe2\x80\x9ccv S\xe2\x80\x9d 是初始化表达式的类型,对于 S 类类型,候选函数的选择如下:
\n\n
\n- 考虑S及其基类的转换函数。那些未隐藏在 S\n 中的非显式转换函数,会生成对 cv2 T2\xe2\x80\x9d 的类型 \xe2\x80\x9clvalue 引用(当初始化\n左值引用或对函数的右值引用时)或 \xe2\ x80\x9ccv2 T2\xe2\x80\x9d 或\n\xe2\x80\x9cr对 cv2 T2\xe2\x80\x9d 的值引用(初始化对函数的右值引用或\n左值引用时),其中 \xe2\x80\ x9ccv1 T\xe2\x80\x9d 与 \xe2\x80\x9ccv2 T2\xe2\x80\x9d 引用兼容,是候选函数。对于直接初始化,那些未隐藏在 S 中并产生对 cv2 T2\xe2\x80\x9d 的类型 \xe2\x80\x9clvalue 引用的显式转换函数(当\n初始化对函数的左值引用或右值引用时) \xe2\x80\x9cr 对 cv2 T2\xe2\x80\x9d 的值引用(初始化右值引用\或对函数的左值引用时),其中 T2 与 T 类型相同或\n可以使用以下命令转换为 T 类型:限定转换,\n也是候选函数。
\n
该项目符号需要一些工作才能正确解析,但它基本上描述了可能适用于此处的两种不相交情况之一:
\n因为operator=(A const&)我们处于情况 #1,并且有一个综合operator A const&()作为唯一的候选者。对于operator=(A&&)情况 #2,非模板operator A()是唯一的候选者。无论哪种方式,我们都有一个明确的隐式转换序列,其中包含用户定义的转换,该转换绑定任一 的引用参数operator=。
但现在operator=根据[over.match.best]中的规则,这两个函数都不是比另一个更好的可行函数。根据[over.ics.rank]中的部分排序,这两种转换都不是更好。
这意味着该程序应因对 的不明确调用而被声明为格式错误operator=。然而,GCC 和 Clang 都犯了错误(尽管不是 MSVC)1。Clang 支持A&&重载,而 GCC 则支持A const&并针对已删除的转换函数的使用发出诊断。但这并不是由于任何标准的强制行为。理想情况下,他们都应该报告呼叫operator=不明确。