Look at this C++ code:
class B;
class A { public: A (B&);};
class B { public: operator A(); };
class C { public: C (B&); };
void f(A) { }
void f(C) { }
int main() {
B b;
f(b);
}
Do you think this compiles? Well, probably not because I'm asking. So here's the error message from Clang 13:
etyp:fun/ $ clang++ test.cpp
test.cpp:10:3: error: call to 'f' is ambiguous
f(b);
^
test.cpp:5:6: note: candidate function
void f(A) { }
^
test.cpp:6:6: note: candidate function
void f(C) { }
^
1 error generated.
Okay so it's ambiguous which conversion gets done for the object b
(of type B
). So what options are there?
1) Call f(A)
. We can call A
's constructor which takes a reference to a B
2) Call f(C)
. We can call C
's constructor which takes a reference to a B
3) Call f(A)
. We can call operator A()
in the class B
But, 1 and 3 are ambiguous because we can't find a better one to call f(A)
. But f(C)
is still not ambiguous - we know how to call it without any ambiguity. Trying to call f(A)
is ambiguous so obviously it shouldn't be chosen.
It turns out in this case, the ambiguity between 1 and 3 is ranked equal priority as the user defined conversion in 2. From the C++20 standard section 12.4.2.10:
For the purpose of ranking implicit conversion sequences as described in 12.4.3.2, the ambiguous conversion sequence is treated as a user-defined conversion sequence that is indistinguishable from any other user-defined conversion sequence.
I find this surprising because I could easily determine that option 2 is the unambiguous way to make this compile.
Not all compilers...
It turns out, MSVC actually accepts the code. You can even see in the assembly it calls f(C)
.
I believe no matter how silly the standard is with its rules (which this particular point may have a very good reason), C++ compilers should aim to conform to the standard. This case was pulled exactly from the standard as a case that should not compile. MSVC should not compile this.
But that's a whole other rant.
Thanks for looking at that C++ code.
Top comments (2)
You must be new to using MSVC++. There's lots of stuff it shouldn't compile, but does; as well as lots of stuff it should compile, but doesn't. It's one of the reasons I avoid anything to do with MS if at all possible.
It was my job for years to add workarounds in a Clang fork for MSVC non-standard behavior. I know.
I do agree with avoiding using MSVC if at all possible, but they have been improving. That's just one small case, and one egregious one since it's directly opposing an exact code snippet in the standard.