首页 > 解决方案 > 添加重载更改选择了哪个重载

问题描述

我试图了解重载选择规则如何导致以下(不直观的)行为。当我具有以下功能时:

#include <iostream>

// Overload 1
template<class T>
void write(T data)
{
  std::cout << "Called write(T data)" << std::endl;
}

// Overload 2
template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
  std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}

int main(int, char**)
{
  int j = 0;
  write(j);
  return 0;
}

选择void write(T data)过载(过载 1)。我认为这对我来说很有意义:重载选择的候选者是void write<T>(T) T = intvoid write<T,U>(T&) T = int, U = <>。两者write(T)write(T&)都将同样专业化,但 Overload 2 有一个空参数包,因此选择了 Overload 1。但是,如果我添加第三个重载:

#include <iostream>

// Overload 0
void write(const int& data)
{
  std::cout << "Called write(const int& data)" << std::endl;
}

// Overload 1
template<class T>
void write(T data)
{
  std::cout << "Called write(T data)" << std::endl;
}

// Overload 2
template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
  std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}

int main(int, char**)
{
  int j = 0;
  write(j);
  return 0;
}

然后突然void write(T&& obj, U&&... objs)(过载 2)被调用。为什么添加没有被选中的重载会改变实际选中的重载?

如果唯一的候选人是void write<T,U>(T&) T = int, U = <>并且void write(const int&)我理解为什么void write<T,U>(T&)会被选中,那么添加额外超载可能会阻止void write(T data)参与超载选择?如果是,为什么?

由于这似乎是编译器特定的行为,因此在 gcc 7.3.0 上观察到了这一点。

一些更有趣的行为:如果函数被重新排序,使得新的重载被放置在原来的两个之间(即重载 1,然后是重载 0,然后是重载 2)然后 gcc 用call of overloaded ‘write(int&)’ is ambiguous. 如果函数被重新排序使得新的重载在最后(即,重载 1,然后是重载 2,然后是重载 0)write(const int& data)则被选中。

标签: c++

解决方案


我认为这是一个 GCC 错误:

重载是:

  • 过载 0:write(const int&)
  • 过载1:write(T) [T=int] -> write(int)
  • 过载2:write(T&&,U&&...) [T=int&,U=[]] -> write(int&)

重载 0 比重载 1 更匹配,因为重载 0 不是模板函数特化。

重载 1 比重载 2 更匹配,因为重载 1 是一个比重载 2 更专业的函数模板。

重载 2 比重载 0 更匹配,因为重载 2 的参数类型的 cv 限定符int&是重载 0: 之一的子集const int&

因此,正如 Clang 所报告的那样,该调用是模棱两可的。


为简化起见,在比较两个函数时,最佳可行函数在此处分 4 步进行评估:

  1. 检查哪个是最好的转换顺序(这里所有的转换都是恒等转换),然后如果转换等级相同:
  2. 检查两个引用绑定之间是否一个比另一个更好,然后如果一个不是引用绑定或者两个绑定不可区分,
  3. 检查一个函数是否是模板特化而另一个不是,那么这两个函数是否是模板特化
  4. 检查其中一个专业是否比另一个专业更专业。

推荐阅读