基于参数的值类别调用函数

Calling function based on the value categories of its arguments

本文关键字:调用 函数 于参数 参数      更新时间:2023-10-16

昨天我问了一个关于何时使用std::forward和何时使用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& a, T& b)函数?

引用绑定和左值到右值转换都被赋予一个精确匹配的秩:

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

当引用类型的形参(8.5.3)直接绑定到实参表达式时,隐式转换序列为单位转换。

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

§14.8.2.4 [temp. deduction .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&& a, T&& b)对于左值也是可行的,但是另一个重载被认为是更专门化的:

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

如果,对于给定类型,两个方向的演绎都成功(即,在上述转换之后类型是相同的)并且 PA都是引用类型(在被上述类型替换之前):

——如果实参模板的类型是左值引用,而形参的类型是左值引用模板不是,则参数类型被认为比另一个更专门化;否则[…]

这使得exp(T& a, T& b)成为左值的首选,而exp(T&& a, T&& b)则是右值的唯一可行。

你的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头文件