Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 862
Constructor Avatar asked Aug 07 '19 14:08

Constructor


People also ask

What is a conversion operator?

A conversion operator, in C#, is an operator that is used to declare a conversion on a user-defined type so that an object of that type can be converted to or from another user-defined type or basic type. The two different types of user-defined conversions include implicit and explicit conversions.

What is a conversion operator in CPP?

Conversion Operators in C++ C++ supports object oriented design. So we can create classes of some real world objects as concrete types. Sometimes we need to convert some concrete type objects to some other type objects or some primitive datatypes. To make this conversion we can use conversion operator.


1 Answers

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.

like image 189
Barry Avatar answered Sep 29 '22 09:09

Barry