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。(@ashepler が正しく指摘したように)localDataは as として宣言されて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 に答える