C++ 编译器必须选择两个转换运算符中的哪一个?
Which of two conversion operators must be selected by C++ compiler?
一个class可以声明多个转换运算符。特别是它可以是某种类型的转换运算符和相同类型的常量引用。如果请求转换为该类型,必须选择两个转换运算符中的哪一个?
考虑一个例子:
#include <iostream>
struct B {};
static B sb;
struct A {
operator B() { std::cout << "operator B() "; return sb; }
operator const B &() { std::cout << "operator const B &() "; return sb; }
};
int main() {
A a;
[[maybe_unused]] B b(a);
}
这里 Clang 选择 operator B()
,MSVC 选择 operator const B &()
,GCC 抱怨选择不明确:
<source>:13:27: error: call of overloaded 'B(A&)' is ambiguous
13 | [[maybe_unused]] B b(a);
| ^
<source>:3:8: note: candidate: 'constexpr B::B(const B&)'
3 | struct B {};
| ^
<source>:3:8: note: candidate: 'constexpr B::B(B&&)'
演示:https://gcc.godbolt.org/z/874h7h3d1
哪个编译器是正确的?
The program is ill-formed and rejected by GCC is correct here but the diagnosis can arguably say it is not complete correct.对于这个声明 B b(a);
,它是根据 [[= 从 A
类型的初始化器 a
直接初始化一个 class B 的对象47=]] p1
Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:
- The converting constructors of T are candidate functions.
- When the type of the initializer expression is a class type “cv S”, conversion functions are considered. The permissible types for non-explicit conversion functions are T and any class derived from T. When initializing a temporary object ([class.mem]) to be bound to the first parameter of a constructor where the parameter is of type “reference to cv2 T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv3 T”, the permissible types for explicit conversion functions are the same; otherwise there are none.
对于转换构造函数,它们是B
的copy/move构造函数,但是,[over.best.ics#general-4]禁止将用户定义的转换序列应用于目标匹配构造函数的参数
However, if the target is
- the first parameter of a constructor or
- [...]
并且构造函数或用户定义的转换函数是
的候选者
- [...]
- [over.match.copy], [over.match.conv], or [over.match.ref] (in all cases), or
- [...]
不考虑用户定义的转换序列。
因此,B
的 copy/move 构造函数不是可行的函数。歧义来自可行函数A::operator B()
和A::operator const B &()
,因为它们的隐式参数对象都是A&
类型,并且对应的参数是A
类型的左值,因此两者都不比另一个好。因此,唯一可以确定哪个更好的机会落在[over.match.best#general-2.2]
the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type.
它们的第二个标准转换序列都是恒等转换,因此它们不是不可区分的。所以,结果是模棱两可的。 GCC 仅在程序模棱两可的地方是正确的,但显然,它的诊断有点误导。 由于 copy/move 构造函数在这种情况下根本不是可行的函数,它们怎么会导致歧义? 如果我们抑制默认移动构造函数的产生,GCC 和 Clang 是此处均不正确,回到您提到的 this question。
一个class可以声明多个转换运算符。特别是它可以是某种类型的转换运算符和相同类型的常量引用。如果请求转换为该类型,必须选择两个转换运算符中的哪一个?
考虑一个例子:
#include <iostream>
struct B {};
static B sb;
struct A {
operator B() { std::cout << "operator B() "; return sb; }
operator const B &() { std::cout << "operator const B &() "; return sb; }
};
int main() {
A a;
[[maybe_unused]] B b(a);
}
这里 Clang 选择 operator B()
,MSVC 选择 operator const B &()
,GCC 抱怨选择不明确:
<source>:13:27: error: call of overloaded 'B(A&)' is ambiguous
13 | [[maybe_unused]] B b(a);
| ^
<source>:3:8: note: candidate: 'constexpr B::B(const B&)'
3 | struct B {};
| ^
<source>:3:8: note: candidate: 'constexpr B::B(B&&)'
演示:https://gcc.godbolt.org/z/874h7h3d1
哪个编译器是正确的?
The program is ill-formed and rejected by GCC is correct here but the diagnosis can arguably say it is not complete correct.对于这个声明 B b(a);
,它是根据 [[= 从 A
类型的初始化器 a
直接初始化一个 class B 的对象47=]] p1
Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:
- The converting constructors of T are candidate functions.
- When the type of the initializer expression is a class type “cv S”, conversion functions are considered. The permissible types for non-explicit conversion functions are T and any class derived from T. When initializing a temporary object ([class.mem]) to be bound to the first parameter of a constructor where the parameter is of type “reference to cv2 T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv3 T”, the permissible types for explicit conversion functions are the same; otherwise there are none.
对于转换构造函数,它们是B
的copy/move构造函数,但是,[over.best.ics#general-4]禁止将用户定义的转换序列应用于目标匹配构造函数的参数
However, if the target is
- the first parameter of a constructor or
- [...]
并且构造函数或用户定义的转换函数是
的候选者
- [...]
- [over.match.copy], [over.match.conv], or [over.match.ref] (in all cases), or
- [...]
不考虑用户定义的转换序列。
因此,B
的 copy/move 构造函数不是可行的函数。歧义来自可行函数A::operator B()
和A::operator const B &()
,因为它们的隐式参数对象都是A&
类型,并且对应的参数是A
类型的左值,因此两者都不比另一个好。因此,唯一可以确定哪个更好的机会落在[over.match.best#general-2.2]
the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type.
它们的第二个标准转换序列都是恒等转换,因此它们不是不可区分的。所以,结果是模棱两可的。 GCC 仅在程序模棱两可的地方是正确的,但显然,它的诊断有点误导。 由于 copy/move 构造函数在这种情况下根本不是可行的函数,它们怎么会导致歧义? 如果我们抑制默认移动构造函数的产生,GCC 和 Clang 是此处均不正确,回到您提到的 this question。