C++11中类型推导中的Rvalue参考

Rvalue Reference in Type Deduction in C++11

本文关键字:Rvalue 参考 类型 C++11      更新时间:2023-10-16

在C++11之前,模板类型的推导非常简单:

template <typename X>
void bar(X i) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

类型X将是调用方传入的任何类型的参数

现在,对于C++11,我读了一篇来自Scott Meyers的关于右值引用的文章。

它表示

template<typename T>
void f(T&& param);

在对作为通用引用的模板参数进行类型推导时,同一类型的左值和右值被推导为具有略微不同的类型。特别地,类型T的左值被推导为类型T&(即,对T的左值引用),而T类型的右值被推导为简单的T类型。

我想知道为什么T类型的左值被推导为T&而T型的右值被推导为简单的T型。

这只是需要记住的东西吗?或

唯一要回答的问题是,为什么T&会导致T&而不是T(就像在"正常"类型的推导中一样)。对此,我想答案是"完美转发"


EDIT:更详细地说,考虑一个完美地转发类构造函数的情况:

struct Widget    //... to stay in Scott Meyers terminology
{
     double &x;
     Widget(double &_x) : x(_x) {}
};
class Manager : public Widget
{
     template<typename ... Args>
     Manager(Args&& ... args) : Widget(std::forward<Args>(args) ...) {}
};

如果您通过调用Manager

double d=1.0;
Manager(d);

根据您提到的规则,Args&&...的类型被推导为double &。由此,Manager类构造函数的行为与类似

Manager(double &d) : Widget(std::forward<double&>(d)) {}

std::forward<double &>(d)的效果基本上是static_cast<double&&>((double &) d),根据参考折叠的规则,它仍然是(double &) (d)。结果,构造函数变成

Manager(double &d) : Widget((double &) d) {}

这样,变量就可以正确地传递给类Widget,并且还可以确保调用正确的构造函数——引用的构造函数。


相反,如果类型不是推断为double &,而是推断为double,则Manager类构造函数的行为将类似

Manager(double &d) : Base(std::forward<double>(d)) {}

转化为

Manager(double &d) : Base((double &&) (d)) {}

也就是说,左值引用被强制转换为右值引用(就像应用了std::move一样)。

这样,可能仍然可以使用引用,因为强制转换没有更改变量的地址。然而,现在您不能确定Widget中是否调用了正确的构造函数——也可能有另一个构造函数使用右值引用,然后会被错误地调用。

我认为原因是T和T&表示作为参数类型出现时的左值;只是T表示左值为局部变量,而T&意思是"来自其他地方的左值的别名"。

一般函数在为T、T&,并且T&amp;,因为后者的语义与lvalues的语义大不相同。所以,如果你想要一个右值,你应该明确地声明它。