MS VC ++上直接和副本初始化的不同行为(使用用户定义的转换运算符)

Con*_*tor 6 c++ initialization conversion-operator language-lawyer c++17

下面的代码编译罚款既克++ 9.1和铛8.0.0(编译标志是-std=c++17 -Wall -Wextra -Werror -pedantic-errors),但不与MSVC 19.22(编译标志是/std:c++17 /permissive-):

struct X{};

struct Bar
{
    Bar() = default;

    Bar(X){}
};

struct Foo
{
    operator X() const
    {
        return X{};
    }

    operator Bar() const
    {
        return Bar{};
    }
};

int main()
{
    Foo foo;
    [[maybe_unused]]Bar b1 = foo; // OK
    [[maybe_unused]]Bar b2(foo);  // failed
}
Run Code Online (Sandbox Code Playgroud)

MSVC编译错误:

<source>(27): error C2668: 'Bar::Bar': ambiguous call to overloaded function
<source>(8): note: could be 'Bar::Bar(Bar &&)'
<source>(7): note: or       'Bar::Bar(X)'
<source>(27): note: while trying to match the argument list '(Foo)'
Run Code Online (Sandbox Code Playgroud)

这是MSVC中的错误吗?

Bar*_*rry 8

我认为这基本上是CWG 2327的体现,其中涉及此示例:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);
Run Code Online (Sandbox Code Playgroud)

问题的症结在于,在这种情况下,我们不允许保证复制省略-因为我们通过Cat的move构造函数,而不是Dog::operator Cat()直接通过via进行初始化。

看来gcc和clang都已经实现了问题的意图 -即要同时对构造函数和转换函数进行重载解析。

在您的示例中:

Bar b2(foo);
Run Code Online (Sandbox Code Playgroud)

按照标准的信中,我们考虑构造函数(和唯一的构造函数) -这是Bar(X),Bar(Bar const&)和Bar(Bar&&)。所有这三个都是可行的,第一个通过Foo::operator X() const,第二个和第三个通过Foo::operator Bar() const。我们可能更愿意Bar(Bar&&),Bar(Bar const&)但我们有办法消除Bar(X)和之间的歧义Bar(Bar&&)。MSVC遵循正确拒绝此初始化的标准。这不是错误。

但是CWG 2327的精神是,这应该Foo::operator Bar() const直接调用,这就是gcc和clang所做的。很难说这是他们一方的错误,因为这可能是我们实际上想要发生的行为,并且很可能是将来某个时候指定它的方式。