2009-12-31

rvalue Reference is not an rvalue

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の説明のさいに、コードを間違えてしまったのだ。

5 comments:

Anonymous said...

> int &&l d = c ;
は、
int && d = c ;
ですね?

江添亮 said...

おっと、わざわざ自前の変換用ツールを使うのが面倒だったので、
手動で書いたら失敗した。修正。

Anonymous said...

lvalueとして扱われるのはどうしてなんでしょうね?何か理由があるのでしょうけど。

struct 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)
にする必要がありますよね、ムーブがかかるようにするためには。冗長に思えます。

江添亮 said...

なぜならば、リファレンスは使うからです。
rvalue 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なのです。

Anonymous said...

うーんなるほど。よくわかりました、ありがとうございます。

vectorなどで構成された戻り値用の構造体で、デフォルトの代入演算子ではムーブがかかってほしいということも思ったんですが、まあ仕方ないですね。