6

我有这段代码,它不能编译,这是预期的。

这是错误:an rvalue reference cannot be bound to an lvalue

class SomeData
{
public:
    vector<int> data;

    SomeData()
    {
        cout << "SomeData ctor" << endl;
        data.push_back(1);
        data.push_back(2);
        data.push_back(3);
    }

    SomeData(const SomeData &other)
    {
        cout << "SomeData copy ctor" << endl;
        data = other.data;
    }

    SomeData(SomeData &&other)
    {
        cout << "SomeData move ctor" << endl;
        data = move(other.data);
    }

    ~SomeData()
    {
        cout << "SomeData dtor" << endl;
    }

    void Print() const
    {
        for(int i : data)
            cout << i;

        cout << endl;
    }
};

void Function(SomeData &&someData)
{
    SomeData localData(someData);
    localData.Print();
}

int main(int argc, char *argv[])
{
    SomeData data;
    Function(data);                       // ERROR

    data.Print();

    return 0;
}

但是,当我变成Function()模板时,它可以正常工作,并使用复制构造函数SomeData代替。

template<class T>
void Function(T &&someData)
{
    T localData(someData);                  // no more error
    localData.Print();
}


这是标准的 C++ 行为吗?

我注意到 Visual Studio 在模板方面往往更加宽容,所以我想知道我是否可以期望所有兼容的 C++11 编译器都有同样的行为。

4

3 回答 3

9

是的。在模板函数的情况下,编译器推导出模板参数T,使其与给定的参数匹配。

因为someData实际上是一个左值,T所以推导为SomeData &。的声明Function,经过类型推导,则变为

void Function(SomeData & &&)

并且SomeData & &&,遵循参考折叠规则,变为SomeData &.

因此,函数参数someData成为左值引用,并按原样传递给localData. 请注意(正如@aschepler 正确指出的那样)localData被声明为T,因此它本身就是 type 的引用SomeData &。因此,这里不会发生复制构造——只是引用的初始化。


如果您想localData成为实际副本,则必须将类型从SomeData &转换为SomeData,即您必须&从类型中删除 。您可以使用以下方法执行此操作std::remove_reference

template<class T>
void Function(T &&someData)
{
   /* Create a copy, rather than initialize a reference: */
    typename std::remove_reference<T>::type localData(someData);
    localData.Print();
}

(为此,您必须这样做#include <type_traits>。)

于 2013-07-17T03:35:05.950 回答
4

这确实是预期的行为。表单模板功能:

template< class T >
Ret Function( T&& param )
{
  ...
}

遵循特殊规则(Ret 可以是或不是模板,没关系)。T&& 被称为通用引用,它基本上可以绑定到任何东西。这是因为当模板推导开始并且参数采用该形式时(注意,vector<T>&&不是通用引用,也不是C<T>任何其他模板参数),引用折叠规则被应用:

T = int& => T&& = int& && 并折叠为单个 int&

完整的对应表是:

& + && = &
&& + & = &
&& + && = &&

所以当你有上述功能时

int a = 5;
int b& = a;
Function( a ); // (1)
Function( b ); // (2)
Function( 3 ); // (3)

在情况 1 中, T = int& 并且推导的类型是 int& (因为 a 是左值),因此 Function 具有以下签名:

Ret Function( int& param ) // int& && collapses to int&

在情况 2 中,T = int&

Ret Function( int& param ) // int& && collapses to int&

在情况 3 中,T = int

Ret Function( int&& param ) 

这个崩溃的规则是委员会发现的合理的转发工作。您可以在这个 Scott Meyers 的视频中找到长篇故事

于 2013-07-17T11:26:34.893 回答
2

只是为了让您放心:这不是 MSVC 问题,这实际上是预期的行为。

在模板中,&&应用于模板类型参数时具有不同的含义。它被称为通用参考

我可以解释一下,但是这篇文章很好地解释了它,所以你应该阅读它。

概括地说(而且非常不准确),如果需要,通用引用有能力“衰减”为普通引用。

于 2013-07-17T03:35:56.163 回答