C ++为什么将左值传递给移动构造函数适用于模板?

时间:2013-07-17 03:27:03

标签: c++ templates visual-studio-2012 c++11

我有这个代码,它不能编译,这是预期的
这是错误: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 ++行为吗?

我注意到视觉工作室在模板方面往往更宽容,所以我想知道是否可以从所有兼容的C ++ 11编译器中获得相同的行为。

3 个答案:

答案 0 :(得分:9)

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

由于someData实际上是左值,T推断为SomeData &。在类型扣除之后,Function的声明变为

void Function(SomeData & &&)
rules for reference collapsing之后,

SomeData & &&变为SomeData &

因此,函数参数someData成为左值引用,并按原样传递给localData的初始化。请注意(正如@aschepler正确指出的那样)localData被声明为T,因此它本身就是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>

(为此,您必须{{1}}。)

答案 1 :(得分:4)

这确实是预期的行为。表单的模板函数:

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

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

T = int&amp; =&GT; T&安培;&安培; = int&amp; &安培;&安培;并且崩溃到一个int&amp;

完整的相关表格是:

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

所以当你有上述功能时

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

在案例1中,T = int&amp;推导出的类型是int&amp; (因为a是左值)因此Function具有以下签名:

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

在案例2中,T = int&amp;

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

在案例3中,T = int

Ret Function( int&& param ) 

这种崩溃规则是委员会认为合理的完美转发工作。您可以在this Scott Meyers's video

中找到长篇故事

答案 2 :(得分:2)

只是向您保证:这不是MSVC问题,这实际上是预期的行为。

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

我可以解释一下,但是this article非常好地解释了它,所以你应该阅读它。

如果需要,通用引用能够“衰减”成普通引用。