问题 为什么这个成员函数的调用含糊不清?


考虑这个课程:

class Base{
public:
    void func(double a) = delete;
    void func(int a) const {}
};

int main(){
    Base base;

    base.func(1);
    return 0;
}

使用clang ++编译时,会产生以下错误:

clang++ --std=c++11 test.cpp 
test.cpp:22:7: error: call to member function 'func' is ambiguous
    base.func(1);

使用g ++,会产生一个警告:

g++ -std=c++11 test.cpp 
test.cpp: In function ‘int main()’:
test.cpp:22:13: warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second: base.func(1);

为什么这段代码含糊不清?


2575
2018-03-03 08:14


起源

整数可以很容易地转换为浮点类型。而 func(int) 是最好的比赛, func(double) 仍然是一个可行的替代方案,使得呼叫模糊不清。 - Some programmer dude
您似乎遇到了一些涉及const和非const成员函数的棘手逻辑。 FWIW,改变 func(double) 到了 const 成员函数删除问题。 - R Sahu
@R Sahu:它没有解决问题:( - Shivendra Agarwal


答案:


非静态成员函数,如下:

void func(double);    // #1
void func(int) const; // #2

也接受了 隐式对象参数 这被认为是超载分辨率([over.match] / P1)与任何其他论点一样:

过载分辨率是一种机制,用于在给定作为调用参数的表达式列表和可根据调用上下文调用的一组候选函数的情况下选择要调用的最佳函数。最佳函数的选择标准是参数的数量,参数与候选函数的参数类型列表的匹配程度, (对于非静态成员函数)对象与隐式对象参数的匹配程度,以及候选函数的某些其他属性。

在将隐式对象参数合并到成员函数签名之后,编译器会看到两个重载:

void func(Base&, double);    // #1
void func(const Base&, int); // #2

并尝试根据调用选择最佳可行功能:

Base base;
base.func(1);

转换自 base (这是类型的非常数左值 Base) 至 Base& 有一个 完全符合 等级(直接参考绑定产生一个 身份转换) - 见 表13。转换自 base 至 const Base& 也是一个 完全符合 但是,排名 [over.ics.rank] /p3.2.6 声明 #1 有一个更好的转换顺序:

- S1和S2是参考绑定([dcl.init.ref]),并且引用引用的类型是除了顶级cv限定符之外的相同类型,并且由S2引用的引用引用的类型比由S1引用的引用引用的类型更加cv限定。 。 [

int f(const int &);
int f(int &);
int g(const int &);
int g(int);

int i;
int j = f(i);    // calls f(int &)
int k = g(i);    // ambiguous

现在为第二个参数,从积分prvalue转换 1 至 double 是一个 浮动积分转换 ([conv.fpint]给出一个 转变 秩。另一方面, 1 至 int 是一个 身份转换 这是一个 完全符合 秩。对于这个论点, #2 被认为具有更好的转换顺序([over.ics.rank] /p3.2.2):

- S1的等级优于S2的等级,或者S1和S2具有相同的等级,并且可以通过下面段落中的规则区分,或者,如果不是,[...]

要成功的过载分辨率要求最多存在一个转换序列不同的参数([over.match.best]):

给定这些定义,如果对于所有参数i,ICS,可行函数F1被定义为比另一个可行函数F2更好的函数一世(F1)不是比ICS更差的转换序列一世(F2),然后

- 对于一些论点j,ICSĴ(F1)是比ICS更好的转换序列Ĵ(F2),或者,如果不是,[...]

在这里,ICS0(#1)优于ICS0(#2),但反过来,ICS1(#2)优于ICS1(#1),因此编译器无法在两个重载之间进行选择并检测歧义。


10
2018-03-03 09:20



我只是想发表一个类似的答案。一个区别:你为什么写 Base*? 13.3.1.4表示隐式对象参数是“对cv X的左值引用”,所以 Base &。 - Werner Henze
很好的答案。只是一点点挑剔: const base& 也直接绑定到类型的参数 base因此对应的隐式转换序列也是一种身份转换;没有资格转换。这只是你引用的子弹中的条件得到满足,这使得另一个转换(到 base&)更好。 - bogdan
@bogdan我不确定这部分,所以非常感谢。 - Piotr Skotnicki


当函数重载时,首先发生重载决策。如果删除的功能是最佳匹配并且被选中,则程序格式错误。

因此,您的程序将产生与以下相同的错误,因为存在从int到double的隐式转换,并且编译器不知道您打算调用哪个函数:

class Base{
public:
    void func(double a) {}
    void func(int a) const {}
};

0
2018-03-03 08:20



你的解释是缺乏的:是的,有一个隐含的转换 int - >double但是 int 这是一个完美的争论。最后的关键在于 const - bolov


因为它 const 修饰语 func(int)。该 base 实例不是const。 C ++编译器似乎找到了 non-const 方法首先如果实例不是const。然后,编译器发现该方法已被删除。所以编译器发出警告。

尝试删除 const 修饰符,或移动 const 修饰符 func(double) 将摆脱警告。

似乎这个警告与隐式转换无关。即使你调用 func 通过 func((int)(1)) 也不好。


-1
2018-03-03 08:25



你错了。它首先没有找到第一种方法。这两个是模棱两可的。 - bolov