【问题标题】:Why is this call to member function ambiguous?为什么这个对成员函数的调用不明确?
【发布时间】:2017-03-03 08:14:10
【问题描述】:

考虑这个类:

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);

为什么这段代码有歧义?

【问题讨论】:

  • 整数可以很容易地转换为浮点类型。虽然func(int) 是最佳匹配,但func(double) 仍然是一个可行的替代方案,这使调用变得模棱两可。
  • 您似乎遇到了一些涉及 const 和非 const 成员函数的棘手逻辑。 FWIW,将 func(double) 更改为 const 成员函数可以解决问题。
  • @R Sahu:它没有解决问题:(

标签: c++ c++11 language-lawyer


【解决方案1】:

非静态成员函数,比如这两个:

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

还接受一个implicit object parameter,它在重载决议([over.match]/p1)中被视为任何其他参数:

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

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

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

并尝试根据调用选择最佳可行函数:

Base base;
base.func(1);

base(这是Base 类型的非常量左值)到Base& 的转换具有精确匹配 等级(直接引用绑定产生Identity conversion)- - 见Table 13。从baseconst Base& 的转换也属于完全匹配 等级,但是[over.ics.rank]/p3.2.6 声明#1 具有更好的转换顺序:

——S1和S2是引用绑定([dcl.init.ref]),引用所引用的类型除了顶级的cv-qualifiers之外是相同的类型,S2初始化的引用所引用的类型更多cv-qualified 比由 S1 初始化的引用所引用的类型。 [ 示例

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

现在对于第二个参数,从整数纯右值 1double 的转换是 浮点整数转换 ([conv.fpint]),它给出了一个转换 em>排名。另一方面,1int 是一个身份转换,它属于完全匹配等级。对于这个论点,#2被认为有更好的转换顺序([over.ics.rank]/p3.2.2):

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

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

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

——对于某些参数 j,ICSj(F1) 是比 ICSj(F2) 更好的转换序列,或者,如果不是这样,[... ]

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

【讨论】:

  • 我只是想发布一个类似的答案。区别之一:你为什么写Base*? 13.3.1.4 说隐式对象参数是“对 cv X 的左值引用”,所以Base &.
  • 优秀的答案。只是一点点吹毛求疵:const base& 也直接绑定到base 类型的参数,因此相应的隐式转换序列也是一个身份转换;没有资格转换。只是您引用的项目符号中的条件得到满足,这使得其他转换(到base&)更好。
  • @bogdan 我不确定这部分,非常感谢。
【解决方案2】:

当一个函数被重载时,重载决议首先发生。如果删除的函数是最佳匹配并被选中,则程序是非良构的。

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

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

【讨论】:

  • 缺少您的解释:是的,存在隐式转换 int->double,但 int 是该论点的完美匹配。最后的关键在于const
【解决方案3】:

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

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

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

【讨论】:

  • 你错了。它没有首先找到第一种方法。两者是模棱两可的。
猜你喜欢
  • 2014-11-18
  • 2020-06-14
  • 2016-05-18
  • 1970-01-01
  • 1970-01-01
  • 2019-11-18
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
相关资源
最近更新 更多