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