重载调用不明确

Call of overload is ambigious

我正在学习新的 C++ 语义,但我遇到了这个程序的错误:

#include <iostream>
#include <string>
#include <utility>

std::string foo(std::string str)
{
    return str + " call from normal";
}

std::string foo(const std::string& str)
{
    return str + " call from normal";
}

std::string foo(std::string&& str)
{
     return str + " call from ref ref";
}

int main()
{
    std::string str = "Hello World!";
    std::string res = foo(str);
    std::string&& res_ref = foo(std::move(str));
    std::cout << "Res ref = " << res_ref << std::endl;
    std::cout << "Str = " << str << std::endl;
    return 0;
}

错误是:

:23:30: error: call of overloaded ‘foo(std::__cxx11::string&)’ is ambiguous
    std::string res = foo(str);

为什么调用有歧义?

当你有;

std::string res = foo(str);

有两个可行的候选人:

foo(std::string );         // #1
foo(std::string const& );  // #2

在给定多个候选函数时,要确定选择哪个函数有很多很多步骤。但在这种情况下,这两种选择是完全无法区分的——在 stringstring const& 之间的重载决议中根本没有对参数的偏好。同样,对于右值参数, stringstring&& 之间没有偏好,因此您的第二次调用也被认为是不明确的。

一般来说,优先选择一个功能的规则与哪个功能更具体有关。例如,给定一个采用 string& 的函数和一个采用 string const& 的函数,前者只能用对 string 的非 const 左值引用来调用,但后者可以用一大堆来调用的东西,所以当两者都可行时,前者是首选(具体来说,由于 [over.ics.rank]/3.2.6)。但在这种情况下,任何你可以调用 #1 的东西,你都可以调用 #2 。任何你可以调用 #2 的东西,你都可以调用 #1 。因此,没有任何理由偏爱其中之一。

你应该简单地删除那个重载,留下你的两个:

foo(std::string const& ); // #2
foo(std::string&& );      // #3

对于左值std::strings,只有#2 是可行的。对于右值 std::strings,两者都是可行的,但首选 #3(根据一般准则,它更具体 - 特别是由于 [over.ics.rank]/3.2.3)。

为清楚起见,忘掉右值引用(字符串&&)吧,假装你是编译器。

定义了两个函数:

  1. foo(std::string str)
  2. foo(const std::string& str)

给定 str 是 std::string 您需要调用:

foo(str);

你要调用哪个函数,1 还是 2?

  1. 您可以将 str 传递给 foo 1。
  2. 您可以将对 str 的常量引用(如指针)传递给 foo 2。

在这种情况下,你可以做任何一个,所以编译器无法决定。

编译器是如何决定的?关于创建候选函数列表、可以对参数执行哪种类型的提升或转换以及可以使用哪些构造函数来创建所需参数的规则。 Here is a simple overview 来自一门课​​程。

这两个函数之间存在歧义:

std::string foo(std::string str); // 1
std::string foo(const std::string& str); // 2

函数名的读法是从右到左。这些函数的英文等价物是:

  1. foo 是一个接受 std::string 参数和 returns std::string.
  2. 的函数
  3. foo 是一个接受 referenceconstant std::string 参数和 returns std::string.
  4. 的函数

分别

编译器已知的这两个函数签名之间的唯一区别是是将 str 的副本作为参数还是对 str 的不可变引用。从编译器的角度来看,这两个函数在运行时没有足够的差异来优先考虑它们。

一般来说,如果它不是原始类型(即 int、char、short 等),请使用引用而不是类型本身。出于所有意图和目的,std::string 类似于 std::vector<char>,因此无论字符串的长度是多少,通过引用传递它总是会花费 sizeof(pointer) 数据交易。