Q.「rvalue参照自体はlvalue」ってどういうこと?
A.rvalue参照(変数に名前が付いていたらその変数)自体は(名前を持っているので名前を持っているものはその型が何だろうと)lvalue であるということです
変数の型の宣言が何だろうととにかく名前の付いているものが lvalue で、名前の付いていないものが rvalue なのです
これは変数の型が std::string&& というような宣言であっても同じことです
名前が付いていたら lvalue です
void foo( std::string&& s ){ // s には s という名前が付いているので lvalue である // s は rvalue参照なので s が指している先は rvalue である ... }
これは rvalue参照変数を別の関数に渡そうとしてみると分かりやすいかと思います
void bar( const std::string& s ){ debug_printf( "bar: %s\n", s.c_str()); } void baz( std::string&& s ){ s.clear(); } void qux( std::string&& s ){ debug_printf( "qux: %s\n", s.c_str()); } void foo( std::string&& s ){ // s には s という名前が付いているので lvalue である // s は rvalue参照なので s が指している先は rvalue である bar( s ); baz( s ); // エラー qux( std::move(s)); } int main(){ foo( "aaa" ); return 0; }
このコードでは baz を呼び出しているところでエラーになりますね
なぜでしょう
rvalue参照というのは参照先を破壊しても構わないという参照です
なぜ破壊しても構わないのかというと参照している先が一時オブジェクトだからですが、
なぜ一時オブジェクトだと破壊しても構わないのかというと、一時オブジェクトはプログラマが作るものではなくコンパイラが作るものであるため、それがそこにあるということは受け取った先以外はコンパイラしか知らないからです
自分しか存在を知らないものなのだからそれをどうしようと誰にも迷惑をかけることはないというわけです
しかし、上記のコードを見てみましょう
baz が渡された s を勝手に壊してしまっていいのでしょうか
上記のコードでは baz に渡された s は baz の後に qux にも渡されていますね
他の人がまだ使うものを勝手に壊してしまってもいいのでしょうか
上記のコードではたまたま問題ありませんが、いつも問題ないと決められるでしょうか
また rvalue参照は壊しても誰にも迷惑をかけないはずでした
しかし baz で s を壊すと qux に迷惑がかかるかもしれません
話が違いますね
なぜでしょう
なぜかと言うとそれは s に s という名前が付いているからなのです
rvalue参照は渡された先とコンパイラしかその存在を知らないから壊しても安心でしたが、今プログラマは s の存在を知っています
そうなるとプログラマは s を baz に渡すだけでなく他にも使うかもしれません
実際に qux に渡しています
これでは s を渡された先である baz は s を安心して壊すことができません
だから std::string&& を期待している baz に s を渡そうとするとエラーになるのです
コンパイラはこの壊しても安心かそうでないかをどこで判断しているのでしょうか
それは s の名前です
s に s という名前が付いていたらそれはプログラマもその存在を知っているという徴なのです
プログラマが知っているものは勝手に壊すことはできません
つまり名前の付いているものは勝手に壊すことできないということです
C++ ではこの名前の付いているもののことを lvalue と呼んでいて、名前の付いていないもののことを rvalue と呼んでいます
名前の付いているものを勝手に壊すことできないということは、名前の付いているものを rvalue参照に渡すことはできないということです
名前の付いているものは rvalue ではなく lvalue なのですから rvalue参照が参照するものとして相応しくないのです
baz に渡している s には s という名前が付いているので rvalue参照を期待している baz には渡せないのです
でも s は qux には渡せていますね
qux の引数も baz と同じ rvalue参照なのにおかしいですね
これはなぜでしょう
これは qux が一番最後にあるからです
qux が呼ばれた後は誰も s を使わないのですから baz と違って qux は s を勝手に壊しても誰にも迷惑はかけません
でもそうは言っても s には s という名前が付いているのですから rvalue参照には渡せないはずでしたね
そうです
コンパイラは単純に名前の有無で判断するのでそのままではやはり s を qux に渡すことはできません
qux に s を渡した後は誰も s を使わないということはコンパイラではなくプログラマが知っていることです
このようなときにプログラマはコンパイラにこれはもう使わないから勝手に壊してもいいよということを伝えることができます
それが std::move です
コンパイラは std::move されたものはもう誰も使わないのだと安心して rvalue参照に渡すようになります
それで qux の呼び出しに std::move(s) を渡しているのです
「rvalue参照自体はlvalue」であるというのは、(rvalue参照型であると宣言されている変数自体には変数名が付けられているので、名前を持っているものはその型が何であろうと lvalue であるから、その)rvalue参照(型の変数)自体は lvalue であるということです