Constructor
Constructor

Reputation: 7473

Different behavior of direct and copy initialization on MS VC++ (using user-defined conversion operators)

The following code compiles fine with both g++ 9.1 and clang 8.0.0 (compilation flags are -std=c++17 -Wall -Wextra -Werror -pedantic-errors), but not with MSVC 19.22 (compilation flags are /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
}

MSVC compilation errors:

<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)'

Is it a bug in MSVC?

Upvotes: 6

Views: 107

Answers (1)

Barry
Barry

Reputation: 302758

I think this is basically a manifestation of CWG 2327, which dealt with this example:

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

Dog d;
Cat c(d);

The crux of the issue was that we don't allow for guaranteed copy elision in this case - because we go through Cat's move constructor rather than just initializing via Dog::operator Cat() directly.

And it seems like gcc and clang both already implement the intent of the issue - which is to do overload resolution on both the constructors and conversion functions at the same time.

In your example:

Bar b2(foo);

Per the letter of the standard, we consider constructors (and only constructors) - which are Bar(X), Bar(Bar const&), and Bar(Bar&&). All three of those are viable, the first by way of Foo::operator X() const and the second and third by way of Foo::operator Bar() const. We can prefer Bar(Bar&&) to Bar(Bar const&) but we have way of disambiguating between Bar(X) and Bar(Bar&&). MSVC is following the standard in correctly rejecting this initialization. It is not a bug.

But the spirit of CWG 2327 is that this should invoke Foo::operator Bar() const directly, which is what gcc and clang do. It is hard to say that this is a bug on their side either since that's probably the behavior we actually want to happen, and will likely be the way it is specified at some point in the future.

Upvotes: 8

Related Questions