为啥这些重载的函数调用模棱两可?

Posted

技术标签:

【中文标题】为啥这些重载的函数调用模棱两可?【英文标题】:Why are these overloaded function calls ambiguous?为什么这些重载的函数调用模棱两可? 【发布时间】:2012-08-06 12:16:31 【问题描述】:

为什么下面的重载函数调用不明确?出现编译错误:

重载'test(long int)'的调用不明确,候选者是:void test(A)| 无效测试(B)|

代码:

class A

    public:
        A(int)
        A()
;

class B: public A

    public:
        B(long)
        B()
;

void test(A a)



void test(B b)



void main()

    test(0L);
    return;

【问题讨论】:

【参考方案1】:

您遇到了一个错误,因为重载解析必须从两个同样可行的函数中进行选择(两者都有用户定义的转换)。函数重载解析是一个非常复杂的主题。有关重载解决方案的更多详细信息,请参见例如这个recent lecture 由 Stephan T. Lavavej 提供。通常最好创建单参数构造函数explicit,然后使用显式构造函数参数调用您的函数。

test(0L) 与任何重载都不完全匹配,因为没有重载 test(long)。您提供的两个重载都对其参数进行了用户定义的转换,但编译器认为它们同样可行。 A 重载必须执行标准转换(long 到 int),然后是用户定义的转换(int 到 A),B 重载用户定义的转换(long 到 B)。但两者都是用户定义的隐式转换序列

这些排名如何?该标准在13.3.3.2 Ranking implicit conversion sequences [over.ics.rank]

中说

标准转换序列 S1 比转换序列更好 如果 S1 是 S2 的适当子序列,则标准转换序列 S2

这些类型的平局,例如如果 A 是 B 的派生类,则适用(反之亦然)。但是这里没有一个转换序列是另一个的子序列。因此它们同样可行,编译器无法解析调用。

class A

public:
    explicit A(int)
    A()
;

class B: public A

public:
    explicit B(long)
    B()
;

void test(A a)


void test(B b)


int main()

    test(A(0L));  // call first overload
    test(B(0L)); // call second overload
    return 0;

注意:这是int main(),而不是void main()

【讨论】:

+1,这是最正式的正确答案,尽管您可以稍微扩展一下解释。 @rhalbersma:但我认为 test(0L) 与 test(B b) 更精确匹配?为什么会模棱两可? 0Llong,所以你的第二段应该说“没有test(long)”。 @huwang 查看更新的答案:只有当 long->B 是 long->int->A 的子序列,反之亦然,B 重载才会更接近。 【参考方案2】:

函数重载会考虑精确的参数类型或隐式转换。 在您的示例中,从重载的角度来看,替代 A(0L) 和 B(0L) 都是相同的,因为需要隐式构造函数调用。

【讨论】:

【参考方案3】:

您正在使用 long 类型的参数调用 test。

没有测试(长)。

编译器必须在 test(A) 和 test(B) 之间进行选择。

调用 test(A) 它有一个 long -> int -> A 的转换序列。

调用 test(B) 它有一个 long -> B 的转换序列。

根据标准的排名规则,如果一个排名比另一个好,它会选择一个 - 或者它会因模棱两可而失败。

在这种特定情况下,两个转换序列的排名相同。

13.3.3“最佳可行函数”部分中,标准中有一长串关于如何计算转换序列排名的规则

【讨论】:

在标准中。我会帮你查的,等一下。 但它没有解释为什么不搜索最接近的潜在转化树。 我已经更新了我的答案,其实我说的太简单了。有一种方法可以对它们进行排名。在这种情况下,两个转换序列的排名是相同的。【参考方案4】:

试试这个:

class A

  public:
  explicit A(int)
  A()
;

关键字显式停止编译器进行隐式转换。

【讨论】:

【参考方案5】:

编译器只允许对用户类型进行一次隐式转换。如果这还涉及原始类型之间的转换,则它们不计算在内。即使您在 test(B) 的情况下进行了两次转换,但以下内容将无法编译:

class B

public:
    B(int) 
;

class A

public:
    A(const B&) 
;

void test(const A&) 

....

test(5);

要禁用编译器进行隐式转换,您应该使用 explicit 关键字和构造函数

【讨论】:

以上是关于为啥这些重载的函数调用模棱两可?的主要内容,如果未能解决你的问题,请参考以下文章

避免在人为的模棱两可的重载函数调用中拼写出类型

为啥 C# 允许通过可选参数进行模棱两可的函数调用?

为啥自动装箱会使 Java 中的某些调用模棱两可?

使用 + (unary plus) 为 lambda 解决函数指针和 std::function 上的模棱两可的重载

为啥具有“相同签名”的模板和非模板函数重载调用非模板函数?

为啥在析构函数中抛出异常时不调用重载删除?