临时值的 C++ 模板类型推导

C++ Template type deduction for temporary value

#include <iostream>
#include <vector>
using namespace std;

template <typename T>
void wrapper(T& u)
{
    g(u);
}

class A {};

void g(const A& a) {}
int main()
{
    const A ca;
    wrapper(ca);
    wrapper(A()); // Error
}

您好,我有一个问题,为什么最后一条语句给出编译器错误。

:18:10: error: cannot bind non-const lvalue reference of type 'A&' to an rvalue of type 'A' wrapper(A());

我认为模板类型 T 会被推断为 const A& 因为 ca 也被推断为 const A&。为什么在这种情况下类型推导会失败?

I thought that the template type T would be deduced as const A& as the ca is also deduced as const A&. Why the type deduction fails in this case?

因为扣除规则不是这样运作的。他们努力推断出尽可能多的与函数参数类型的匹配。临时对象不一定是 const,它可以绑定到 const 引用。

但是您的函数模板不接受 const 引用,而是接受 non-const 左值引用。所以没有 const 会 spring 除非函数参数是 const 本身(它不是)。

正确的解决方案是使用转发引用(对推导的模板参数的右值引用):

template <typename T>
void wrapper(T&& u)
{
    g(std::forward<T>(u));
}

现在u可以绑定到任何对象。推导的类型会告诉您函数参数的值类别,允许您将该类别转发给函数调用 g(...),并确保选择了正确的重载。


顺便说一句,如果你很好奇,如果你直接将 const 添加到临时类型,你的原始代码将构建得很好。

using CA = A const;
wrapper(CA()); // Not an error anymore

现在 u 最终成为 A const& 并且绑定到临时文件就好了。但这只是一种好奇心,在实践中不太可能有用。使用转发引用。

Why the type deduction fails in this case?

它没有失败。 T 推导为 A。因此,参数u的类型是A&

wrapper() 的调用实际上失败了,因为 rvalue(即:A() 在这种情况下)不能绑定到非const 左值引用(即:参数u)。

错误消息试图传达:

const 引用无法绑定到临时值,因为临时对象的生命周期在控件到达函数之前就已经过期。

  • 变量ca不是临时变量,因为它有一个可以在main.
  • 中引用的名字
  • 它的生命周期不会在 main 结束之前到期,因此可以在 wrapper.
  • 中安全地引用它
  • 模板类型推导不会删除 const,因为它会违反 const-ca 的正确性。

对于 wrapper(A());,类型参数 T 将推导为 A。由于临时没有 const-ness,因此参数 u 将被推断为 A&,但由于非 const 引用无法绑定到临时值由于上述原因,没有 wrapper 函数可以使用参数 A().

来调用

现在,C++ 具有延长临时对象生命周期的功能,如果您只是从中读取的话。这就是 const T& 绑定到临时对象的原因。在你的例子中,无名临时对象的生命周期被延长到 wrapper 函数结束。

如果您将类型参数显式设置为 const T&,则如下:

template <typename T>
void wrapper(const T& u)
{
    g(u);
}

class A {};

void g(const A& a) {}

int main()
{
    const A ca;
    wrapper(ca);
    wrapper(A()); // Error no more.
}

编译得很好。


[C++11]

对于wrapper(A());,类型参数T仍然会推导为A,而参数u的类型为A&&,称为对 A 的右值引用。 假设我们向 wrapper 添加另一个重载,以便如下:

template <typename T>
void wrapper(const T& u)
{
    g(u);
}
template <typename T>
void wrapper(T&& u)
{
    g(u);//u is an lvalue since it has a name.
}

存在。

您可以预期 wrapper(A()); 可以编译,更喜欢右值重载,因为右值引用是 wrapper 中的左值,因为它有一个名称。