C++为什么将左值传递给move构造函数对模板有效

C++ why does passing an lvalue to a move constructor work for templates?

本文关键字:构造函数 有效 move 为什么 值传 C++      更新时间:2023-10-16

我有这段代码,它没有编译,这是意料之中的事

这是错误: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++行为吗?

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

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

由于someData实际上是一个左值,因此T被推导为SomeData &Function的声明,经过类型推导,然后变成

void Function(SomeData & &&)

并且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>。)

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

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

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

T=int&=>T&amp;=int&amp&并且塌陷为单个int&

完整的corrispondence表是:

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

所以当你有上述功能

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

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

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的视频中找到长话短说

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

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

我可能会转述一下,但这篇文章解释得很好,所以你应该读一读

概括起来(而且非常不精确),如果需要,通用引用可以"衰减"为普通引用。

相关文章: