【问题标题】:Calling function based on the value categories of its arguments根据参数的值类别调用函数
【发布时间】:2015-09-03 06:56:40
【问题描述】:

昨天我问a question about when to use std::forward and when to use std::move

今天我试图应用我认为我学到的东西。我写了以下内容:

template <typename T>
void exp(T a, T b)
{
  cout << "rvalues" << endl;
}

template <typename T>
void exp(T& a, T& b)
{
  cout << "lvalues" << endl;
}

template <typename T>
void foo(T&& a, T&& b)
{
  exp(forward<T>(a), forward<T>(b));
}

当我在main 中调用foo(4, 5) 时,它会打印出"rvalue",正如我所期望的那样,但是当我执行类似的操作时

int a = 0, b = 0;
foo(a, b);

发生错误:'exp' : ambiguous call to overloaded function

我在这里缺少什么?为什么最后一次调用foo(a, b) 没有调用void exp(T&amp; a, T&amp; b) 函数?

【问题讨论】:

  • 你会选择什么函数?
  • 在我看来,当我调用foo(a, b) 时,我认为程序应该选择void exp(T&amp; a, T&amp; b) 函数。无论如何,我用另一个问题编辑了描述,谢谢!
  • 为什么不void exp(T, T)
  • 因为ab 是左值引用,forward 将保留exp 的左值引用...
  • 没有一个重载比另一个重载更专业,并且两者都适用于左值参数

标签: c++ c++11


【解决方案1】:

引用绑定和左值到右值的转换都被赋予了精确匹配等级:

§ 13.3.3.1.4 [over.ics.ref]/p1:

当引用类型的参数直接(8.5.3)绑定到参数表达式时,隐式转换序列是身份转换。

因此,编译器无法根据更好的转换序列选择在两者之间进行选择,并尝试对exp的两个重载进行部分排序(因为更专业的函数模板优先于重载解析)。然而:

§ 14.8.2.4 [temp.deduct.partial]/p5:

在完成偏序之前,对用于偏序的类型进行某些转换:

— 如果P 是引用类型,则P 将替换为引用的类型。

— 如果A 是引用类型,则A 将替换为引用的类型。

这使得这两个重载无法区分,因为两者都不是更专业的,因为从偏序的角度来看,它们看起来相同,并且没有其他例外适用。

如果您的主要目标是为右值设置一个重载,为左值设置另一个重载,您可以将它们定义如下:

template <typename T>
void exp(T&& a, T&& b) {}

template <typename T>
void exp(T& a, T& b) {}

现在,虽然exp(T&amp;&amp; a, T&amp;&amp; b) 也适用于左值,但另一个重载被认为更专业

§ 14.8.2.4 [temp.deduct.partial]/p9:

如果对于给定类型,双向推导成功(即,在上述转换后类型相同)并且PA 都是引用类型(在被替换之前与上面提到的类型):

——如果参数模板的类型是左值引用并且参数的类型是 模板不是,参数类型被认为比另一个更专业;否则[...]

这使得exp(T&amp; a, T&amp; b) 成为左值的首选,而exp(T&amp;&amp; a, T&amp;&amp; b) 反过来成为右值的唯一可行。

DEMO

【讨论】:

  • 如果我错了,请纠正我,但此时,如果我从foo 函数中删除前向部分,则唯一调用的函数将是左值专用函数。我说的对吗?
  • 我明白了。我问这个只是为了好奇
  • @Agostino 是的,没有std::forward,传递给exp 的参数的值类别是左值
  • 好的。谢谢你的解释,它真的帮助了我
  • 反正我一开始并没有在exp函数中使用T&amp;&amp;,因为我知道它是一个通用引用,因此不是专门的。
【解决方案2】:

打印右值的 exp 版本不正确。 应该是:

template <typename T>
void exp(T&& a, T&& b)
{
  cout << "rvalues" << endl;
}

如果您调用 foo(a, b) 编译器可能会选择复制 a 和 b 并调用您的右值版本,而实际上它们不是右值。 这就是您收到编译错误的原因。

如果您真的想查看参数是否为右值,您可以使用 is_rvalue_reference:

template <typename T>
void exp(T&& a, T&& b)
{
  std::cout << "rvalues: " << std::is_rvalue_reference<T&&>::value << std::endl;
}

如果您想要一个用于右值的函数和一个用于左值的函数,那么您可以使用 enable_if:

template <typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type exp(T&& a, T&& b)
{
  std::cout << "rvalues" << std::endl;
}

template <typename T>
typename std::enable_if<!std::is_rvalue_reference<T&>::value, void>::type exp(T& a, T& b)
{
  std::cout << "lvalues" << std::endl;
}

您可能需要包含 type_traits 头文件。

【讨论】:

  • 第二个也应该是(T&amp;&amp; , T&amp;&amp;)
  • 或直接将enable_if替换为void
  • 抱歉,已修正答案,应该在第二个 is_rvalue_reference 中使用 T&。