This C++ code doesn't look ambiguous to me


Look at this C++ code:

test.cpp

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.