H Bellamy
H Bellamy

Reputation: 22715

Is MSVC right to find this method call ambiguous, whilst Clang/GCC don't?

Clang (3.9.1) and GCC (7, snapshot) print "1", "2" to the console when this code is run.

However, MSVC fails to compile this code:

source_file.cpp(15): error C2668: 'Dictionary::set': ambiguous call to overloaded function

source_file.cpp(9): note: could be 'void Dictionary::set(int64_t)'

source_file.cpp(8): note: or 'void Dictionary::set(const char *)'

source_file.cpp(15): note: while trying to match the argument list '(const unsigned int)'

#include <iostream>

static const unsigned ProtocolMajorVersion = 1;
static const unsigned ProtocolMinorVersion = 0;

class Dictionary {
public:
    void set(const char *Str) { std::cout << "1"; }
    void set(int64_t val) { std::cout << "2"; }
};

int main() {
    Dictionary dict;
    dict.set(ProtocolMajorVersion);
    dict.set(ProtocolMinorVersion);
}

I think MSVC is right - the value of ProtocolMajorVersion is 0, which can be NULL or int64_t(0).

However, this seems to be the case when replacing

dict.set(ProtocolMinorVersion)

with

dict.set(0);

source_file.cpp:15:10: error: call to member function 'set' is ambiguous dict.set(0);

source_file.cpp:8:10: note: candidate function

void set(const char *Str) { std::cout << "1"; }

source_file.cpp:9:10: note: candidate function

void set(int64_t val) { std::cout << "2"; }

So what's going on here - which compiler is right? Would surprise me if both GCC and Clang are accepting incorrect code, or is MSVC just being buggy? Please refer to the standard

Upvotes: 3

Views: 587

Answers (2)

dyp
dyp

Reputation: 39141

In C++11 and before, any integral constant expression which evaluates to 0 is a considered a null pointer constant. This has been restricted in C++14: only integer literals with value 0 are considered. In addition, prvalues of type std::nullptr_t are null pointer constants since C++11. See [conv.ptr] and CWG 903.

Regarding overload resolution, both the integral conversion unsigned -> int64_t and the pointer conversion null pointer constant -> const char* have the same rank: Conversion. See [over.ics.scs] / Table 12.

So if ProtocolMinorVersion is considered a null pointer constant, then the calls are ambiguous. If you just compile the following program:

static const unsigned ProtocolMinorVersion = 0;

int main() {
    const char* p = ProtocolMinorVersion;
}

You will see that clang and gcc reject this conversion, whereas MSVC accepts it.

Since CWG 903 is considered a defect, I'd argue that clang and gcc are right.

Upvotes: 6

Mats Petersson
Mats Petersson

Reputation: 129494

When two compilers agree and one doesn't, it's nearly always the one that doesn't that is wrong.

I would argue that if you declare a value as const unsigned somename = 0;, it is no longer a simple zero, it is a named unsigned constant with the value zero. So should not be considered equivalent to a pointer type, leaving only one plausible candidate.

Having said that, BOTH of the set functions require conversion (it's not a uint64_t, neither a const char *), so one could argue that MSVC is right [the compiler shall pick the type that requires least conversion, if multiple types require equal amount of conversion, it's ambiguous] - although I still don't think the compiler should accept a named constant of the value zero as an equivalent to a pointer...

Sorry, probably more of a "comment" than an answer - I started writing with the intention of saying "gcc/clang are right", but then thinking more about it came to the conclusion that "although I would be happier with that behaviour, it's not clear that this is the CORRECT behaviour".

Upvotes: 1

Related Questions