由于删除了模板化的左值转换运算符,clang 或 gcc 中可能存在错误?
Possible bug in clang or gcc because of deleted templated lvalue conversion operator?
这是 问题的后续。
我玩了这段代码并使用了 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;
}
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;
|
^~~~~~~~
那么,哪个编译器是对的,哪个是错的呢?
我想看看gcc是不是错误,但是我该如何克服这个错误?
Clang 和 GCC 都错了,但至少 GCC 停止了编译,所以稍微好一点。
A
是 class 类型,因此分配给它会通过 operator=
重载。你没有提供一个,所以编译器提供了两个,达到
的效果
A& operator=(A const&) = default;
A& operator=(A&&) = default;
该引用参数需要从表达式 c.a = t
中的 t
进行初始化。对于任一 operator=
,引用都可以明确绑定。
[dcl.init.ref]
5 A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
- [...]
- has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type
“cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (this
conversion is selected by enumerating the applicable conversion
functions ([over.match.ref]) and choosing the best one through
overload resolution),
then the reference is bound ... to the lvalue result of the
conversion in the second case (or, in either case, to the appropriate
base class subobject of the object).
Otherwise, if the initializer expression
- [...]
- has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function
lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with
“cv3 T3” (see [over.match.ref]),
then ... the result of the conversion in the second case is called
the converted initializer. If the converted initializer is a prvalue,
its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the
temporary materialization conversion ([conv.rval]) is applied. In any
case, the reference is bound to the resulting glvalue (or to an
appropriate base class subobject).
关于为这两种情况设置的候选建筑主题,标准说
[over.match.ref]
1 Under the conditions specified in [dcl.init.ref], a reference can
be bound directly to the result of applying a conversion function to
an initializer expression. Overload resolution is used to select the
conversion function to be invoked. Assuming that “reference to cv1 T”
is the type of the reference being initialized, and “cv S” is the type
of the initializer expression, with S a class type, the candidate
functions are selected as follows:
- The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
and yield type “lvalue reference to cv2 T2” (when initializing an
lvalue reference or an rvalue reference to function) or “cv2 T2” or
“rvalue reference to cv2 T2” (when initializing an rvalue reference or
an lvalue reference to function), where “cv1 T” is
reference-compatible with “cv2 T2”, are candidate functions. For
direct-initialization, those explicit conversion functions that are
not hidden within S and yield type “lvalue reference to cv2 T2” (when
initializing an lvalue reference or an rvalue reference to function)
or “rvalue reference to cv2 T2” (when initializing an rvalue reference
or an lvalue reference to function), where T2 is the same type as T or
can be converted to type T with a qualification conversion, are also
candidate functions.
该项目符号需要一些工作才能正确解析,但它基本上描述了可能适用于此的两种不相交情况之一:
- 初始化对 T 的左值引用
- 候选函数是那些产生“对 cv2 T2 的左值引用”的函数。
- 初始化对 T 的右值引用
- 候选函数是那些产生“cv2 T2”或“对 cv2 T2 的右值引用”的函数。
对于 operator=(A const&)
,我们属于案例 #1,并且有一个合成的 operator A const&()
作为唯一的候选者。对于 operator=(A&&)
它的案例 #2,非模板 operator A()
是唯一的候选者。无论哪种方式,我们都有一个明确的隐式转换序列,其中包含绑定 operator=
.
的引用参数的用户定义转换
但现在根据 [over.match.best]. And neither conversion is better according to the partial ordering in [over.ics.rank] 中的规则,operator=
都不是比另一个更好的可行函数。
这意味着由于对 operator=
的不明确调用,程序应被声明为格式错误。然而,GCC 和 Clang 都犯了错误(虽然不是 MSVC)1。 Clang 支持 A&&
重载,而 GCC 支持 A const&
并针对使用已删除的转换函数发出诊断。但这不是由于任何标准的强制行为。理想情况下,他们都应该报告对 operator=
的调用不明确。
1 - A comparison of different compiler behaviors, with a reduced example.
这是
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;
}
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;
|
^~~~~~~~
那么,哪个编译器是对的,哪个是错的呢?
我想看看gcc是不是错误,但是我该如何克服这个错误?
Clang 和 GCC 都错了,但至少 GCC 停止了编译,所以稍微好一点。
A
是 class 类型,因此分配给它会通过 operator=
重载。你没有提供一个,所以编译器提供了两个,达到
A& operator=(A const&) = default;
A& operator=(A&&) = default;
该引用参数需要从表达式 c.a = t
中的 t
进行初始化。对于任一 operator=
,引用都可以明确绑定。
[dcl.init.ref]
5 A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
- [...]
- has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions ([over.match.ref]) and choosing the best one through overload resolution),
then the reference is bound ... to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).
Otherwise, if the initializer expression
- [...]
- has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see [over.match.ref]),
then ... the result of the conversion in the second case is called the converted initializer. If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied. In any case, the reference is bound to the resulting glvalue (or to an appropriate base class subobject).
关于为这两种情况设置的候选建筑主题,标准说
[over.match.ref]
1 Under the conditions specified in [dcl.init.ref], a reference can be bound directly to the result of applying a conversion function to an initializer expression. Overload resolution is used to select the conversion function to be invoked. Assuming that “reference to cv1 T” is the type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:
- The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” (when initializing an lvalue reference or an rvalue reference to function) or “cv2 T2” or “rvalue reference to cv2 T2” (when initializing an rvalue reference or an lvalue reference to function), where “cv1 T” is reference-compatible with “cv2 T2”, are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” (when initializing an lvalue reference or an rvalue reference to function) or “rvalue reference to cv2 T2” (when initializing an rvalue reference or an lvalue reference to function), where T2 is the same type as T or can be converted to type T with a qualification conversion, are also candidate functions.
该项目符号需要一些工作才能正确解析,但它基本上描述了可能适用于此的两种不相交情况之一:
- 初始化对 T 的左值引用
- 候选函数是那些产生“对 cv2 T2 的左值引用”的函数。
- 初始化对 T 的右值引用
- 候选函数是那些产生“cv2 T2”或“对 cv2 T2 的右值引用”的函数。
对于 operator=(A const&)
,我们属于案例 #1,并且有一个合成的 operator A const&()
作为唯一的候选者。对于 operator=(A&&)
它的案例 #2,非模板 operator A()
是唯一的候选者。无论哪种方式,我们都有一个明确的隐式转换序列,其中包含绑定 operator=
.
但现在根据 [over.match.best]. And neither conversion is better according to the partial ordering in [over.ics.rank] 中的规则,operator=
都不是比另一个更好的可行函数。
这意味着由于对 operator=
的不明确调用,程序应被声明为格式错误。然而,GCC 和 Clang 都犯了错误(虽然不是 MSVC)1。 Clang 支持 A&&
重载,而 GCC 支持 A const&
并针对使用已删除的转换函数发出诊断。但这不是由于任何标准的强制行为。理想情况下,他们都应该报告对 operator=
的调用不明确。
1 - A comparison of different compiler behaviors, with a reduced example.