rvalue referenceは、rvalueではない。私は今まで、これを深く考えたことはなかった。一体どういうことなのか。
int a ; // Error int && b = a ; // OK. the same thing std::move() does. int && c = static_cast< int && >(a) ; // Error. Why? int && d = c ;
bはエラーになるのは、常識でも理解できる。しかし、なぜdがエラーになるのか。まったく同じ型ではないか。また、こんな例もある。
// lvalue reference void f( int & ) {} // rvalue reference void f( int && ) {} int main() { int x = 0 ; int && ref = std::move(x) ; // calls lvalue reference overload of f() f(ref) ; }
なぜだろうか。rvalue referenceを渡しているのに、lvalue referenceのオーバーロードが呼ばれるとは、不思議である。さっそく、規格にあたろう。
8.5.3 References [dcl.init.ref] p5に、以下のように書いてある。
Otherwise, ... or the reference shall be an rvalue reference and the initializer expression shall be an rvalue.
rvalue referenceは、rvalueで初期化しなければならない。ところで、rvalue reference自身は、rvalueではない。lvalueである。よって、rvalue referenceを、rvalue referenceで初期化することは出来ない。
やれやれ、これで、C++0x本を書くときは、正しく説明することが出来る。自分でも、いまいちこの辺を理解していなかったので、ref-qualiferの説明のさいに、コードを間違えてしまったのだ。
> int &&l d = c ;
ReplyDeleteは、
int && d = c ;
ですね?
おっと、わざわざ自前の変換用ツールを使うのが面倒だったので、
ReplyDelete手動で書いたら失敗した。修正。
lvalueとして扱われるのはどうしてなんでしょうね?何か理由があるのでしょうけど。
ReplyDeletestruct S1
{
std::vector vec;
void operator=(S1&& r){ vec= r.vec; }
}
S1 Get()
{
S1 s1;
return s1;
}
S1 s1= Get();
ここで
vec= std::move(r.vec)
にする必要がありますよね、ムーブがかかるようにするためには。冗長に思えます。
なぜならば、リファレンスは使うからです。
ReplyDeletervalue referenceの変数がrvalueだと解釈されるとすると、
// コンストラクターなどを省略
struct X { int * p ; } ;
// Xを内部でムーブしてしまう関数
void g( X && x ) ;
void f( X && x )
{
g( x ) ; // rvalue referenceがrvalueだと、問題なく呼ばれてしまう
*x.p = 0 ; // エラーになるかもしれない
}
関数fの本体で関数gを呼んだ場合、関数gの本体が、すでにxをムーブしてしまう、つまり、Xのメンバーであるポインターを横取りしてしまうかもしれません。
とすると、関数gから戻った後のx.pは、妥当なオブジェクトを参照するポインターではなくなっているかもしれません。
だから、rvalue referenceはlvalueなのです。
うーんなるほど。よくわかりました、ありがとうございます。
ReplyDeletevectorなどで構成された戻り値用の構造体で、デフォルトの代入演算子ではムーブがかかってほしいということも思ったんですが、まあ仕方ないですね。