MSVC 发现此方法调用模棱两可,而 Clang/GCC 则不然,这是否正确?

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

提问人:H Bellamy 提问时间:2/27/2017 最后编辑:H Bellamy 更新时间:2/27/2017 访问量:523

问:

运行此代码时,Clang (3.9.1) 和 GCC (7, snapshot) 将“1”、“2”打印到控制台。

但是,MSVC 无法编译此代码:

source_file.cpp(15):错误 C2668:“Dictionary::set”:对重载函数的调用不明确

source_file.cpp(9):注意:可以是'void Dictionary::set(int64_t)'

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

source_file.cpp(15):注意:在尝试匹配参数列表“(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);
}

我认为 MSVC 是对的 - 的值是 ,可以是 或 。ProtocolMajorVersion0NULLint64_t(0)

但是,在更换时似乎就是这种情况

dict.set(ProtocolMinorVersion)

dict.set(0);

source_file.cpp:15:10:错误:对成员函数“set”的调用不明确 dict.set(0);

source_file.cpp:8:10:注意:候选函数

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

source_file.cpp:9:10:注意:候选函数

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

那么这是怎么回事 - 哪个编译器是正确的?如果 GCC 和 Clang 都接受不正确的代码,或者 MSVC 只是有问题,我会感到惊讶吗?请参考标准

C GCC visual-C++ Clang 语言-律师

评论

2赞 Bok McDonagh 2/27/2017
它是模棱两可的,因为类型不匹配,所以它也可能是。“unsigned”的大小不一定是 64 位,因此它不太可能与in64_t类型匹配。就我个人而言,我更喜欢 MSVC 的方式 - 意识到 C/C++ 程序中的歧义总是好的。

答:

1赞 Mats Petersson 2/27/2017 #1

当两个编译器同意而一个编译器不同意时,几乎总是那个不这样做的编译器是错误的。

我认为,如果你将一个值声明为 ,它不再是一个简单的零,它是一个值为零的命名无符号常量。因此,不应被视为等同于指针类型,只留下一个合理的候选者。const unsigned somename = 0;

话虽如此,这两个函数都需要转换(它不是 a ,也不是 a),所以有人可能会争辩说 MSVC 是对的 [编译器应选择需要最少转换的类型,如果多个类型需要相等量的转换,则它是模棱两可的] - 尽管我仍然认为编译器不应该接受值为零的命名常量作为指针的等价物......setuint64_tconst char *

对不起,可能更多的是“评论”而不是答案——我开始写这篇文章的目的是说“gcc/clang 是对的”,但后来想了想,得出的结论是“虽然我会对这种行为更满意,但不清楚这是正确的行为”。

评论

2赞 dyp 2/27/2017
整数转换(如 -> )和指针转换(如 -> )具有相同的等级:转换。在 C++11 中,任何作为空指针常量的常量整数表达式都可用于初始化指针;这在 C++14 中已受到限制,请参见 wg21.link/cwg903intlong long0char*
0赞 H Bellamy 2/27/2017
@dyp想把这个评论变成答案吗?
2赞 M.M 2/27/2017
不同意第一段,这是一个非常弱的启发式 IMO
6赞 dyp 2/27/2017 #2

在 C++11 及更早版本中,任何计算结果为 0 的整数常量表达式都被视为空指针常量。这在 C++14 中受到限制:只考虑值为 0 的整数文本。此外,类型的 pr值是自 C++11 以来的空指针常量。参见 [conv.ptr] 和 CWG 903std::nullptr_t

关于重载分辨率,整数转换 -> 和指针转换空指针常量 -> 具有相同的秩:Conversion。参见 [over.ics.scs] / 表 12。unsignedint64_tconst char*

因此,如果被视为空指针常量,则调用是不明确的。如果您只编译以下程序:ProtocolMinorVersion

static const unsigned ProtocolMinorVersion = 0;

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

您将看到 clang 和 gcc 拒绝此转换,而 MSVC 接受它。

由于 CWG 903 被认为是一个缺陷,我认为 clang 和 gcc 是对的。

评论

0赞 T.C. 2/28/2017
尽管这可能很有趣,但我认为给定 ,有一个空指针常量有点太:)如果没有CWG 903,C++11的这一部分是无意义的。struct S { int x; }S().x