move semantics に関するメモ

多分間違いもあるので鵜呑み厳禁。
→大量に間違いがあるのでこのエントリは無かったことにしましょう。

左辺値

struct X { };
X x; // x は左辺値

左辺値参照型の左辺値

struct X { } x;
X& rx = x; // rx は左辺値参照型の左辺値

左辺値参照型の右辺値

struct X { } x;
X& f(X& v) { return v; }
f(x); // f(x) の戻り値は左辺値参照型の右辺値

右辺値

struct X { };
X(); // X() は右辺値
X foo() { return X(); }
foo(); // foo() の戻り値は右辺値

右辺値参照型の左辺値

struct X { };
X&& x = X(); // x は右辺値参照型の左辺値

右辺値参照型の右辺値

struct X { };
X&& f(X&& v) { return v; }
f(X()); // f(X()) の戻り値は右辺値参照型の右辺値

こんなに区別する必要あんの?

左辺値参照型の左辺値と左辺値参照型の右辺値は実際のところ区別する必要が無かった気がする。
あと何か左辺値と左辺値参照型の左辺値とか、右辺値と右辺値参照型の右辺値とかも区別しなくてもいけそうな気がする。
そう考えると、

  • 左辺値、左辺値参照型の左辺値、左辺値参照型の右辺値
  • 右辺値参照型の左辺値
  • 右辺値、右辺値参照型の右辺値

の 3 つに区別するだけでいけそうな気もする。
でもまあとりあえず分けて考えてみよう。

std::move()

渡す値が、右辺値、右辺値参照型の左辺値、右辺値参照型の右辺値の場合

右辺値参照型の左辺値で受けて、右辺値参照型の右辺値に変換する

std::move(X()); // 右辺値→右辺値参照型の右辺値

X&& x = X();
std::move(x); // 右辺値参照型の左辺値→右辺値参照型の右辺値

X&& f(X&& v) { return v; }
std::move(f(X())); // 右辺値参照型の右辺値→右辺値参照型の右辺値

ただ、右辺値、右辺値参照型の右辺値はそのまま使えばいいだけなので、右辺値参照型の左辺値の場合しか使わないと思う。

渡す値が、左辺値、左辺値参照型の左辺値、左辺値参照型の右辺値の場合

左辺値参照型の左辺値で受けて、右辺値参照型の右辺値に変換する

X x;
std::move(x); // 左辺値→右辺値参照型の右辺値

X& rx = x;
std::move(rx); // 左辺値参照型の左辺値→右辺値参照型の右辺値

X& f(X& v) { return v; }
std::move(f(x)); // 左辺値参照型の右辺値→右辺値参照型の右辺値


要するに何でも右辺値参照型の右辺値に変換する

std::forward()

渡す値が、右辺値、右辺値参照型の左辺値、右辺値参照型の右辺値の場合

右辺値参照型の左辺値で受けて、右辺値参照型の右辺値に変換する

std::forward<X>(X()); // 右辺値→右辺値参照型の右辺値

X&& x = X();
std::forward<X>(x); // 右辺値参照型の左辺値→右辺値参照型の右辺値

X&& f(X&& v) { return v; }
std::forward<X>(f(X())); // 右辺値参照型の右辺値→右辺値参照型の右辺値

ここは std::move() と同じ

渡す値が、左辺値、左辺値参照型の左辺値、左辺値参照型の右辺値の場合

左辺値参照型の左辺値で受けて、左辺値参照型の右辺値に変換する

X x;
std::forward<X>(x); // 左辺値→左辺値参照型の右辺値

X& rx = x;
std::forward<X>(rx); // 左辺値参照型の左辺値→左辺値参照型の右辺値

X& f(X& v) { return v; }
std::forward<X>(f(x)); // 左辺値参照型の右辺値→左辺値参照型の右辺値


要するに右辺値参照は右辺値参照のまま、左辺値参照は左辺値参照のままにする。

テンプレートでない右辺値参照の引数で確実に取れるのは、右辺値か右辺値参照型の右辺値のみ

struct X { };
void foo(X&) { };
void foo(X&&) { };

X bar() { return X(); }
X& bar2(X& v) { return v; }
X&& bar3(X&& v) { return v; }

X x;
foo(x); // foo(X&), x は左辺値
foo(X()); // foo(X&&), X() は右辺値
foo(bar()); // foo(X&&), bar() の戻り値は右辺値
X& rx = x;
foo(x); // foo(X&), rx は左辺値参照型の左辺値
foo(bar2(x)); // foo(X&), bar2(x) の戻り値は左辺値参照型の右辺値
X&& mx = X();
foo(mx); // foo(X&), mx は右辺値参照型の左辺値
foo(bar3(X())); // foo(X&&), bar3(X()) の戻り値は右辺値参照型の右辺値

foo(X&) の関数をコメントアウトすると、今の仕様の実装が無いから実験できないけど、多分こうなる

struct X { };
void foo(X&) { };
void foo(X&&) { };

X bar() { return X(); }
X& bar2(X& v) { return v; }
X&& bar3(X&& v) { return v; }

X x;
foo(x); // Error, 左辺値から右辺値参照型へ変換できない
foo(X()); // foo(X&&), X() は右辺値
foo(bar()); // foo(X&&), bar() の戻り値は右辺値
X& rx = x;
foo(rx); // Error, 左辺値参照型の左辺値から右辺値参照型へ変換できない
foo(bar2(x)); // Error, 左辺値参照型の右辺値から右辺値参照型へ変換できない
X&& mx = X();
foo(mx); // foo(X&&), 右辺値参照型の左辺値をそのまま渡すだけ
foo(bar3(X())); // foo(X&&), bar3(X()) の戻り値は右辺値参照型の右辺値

テンプレートだと T&& が T& に変換されたりするので事情が変わってくる。
左辺値、左辺値参照型の左辺値、左辺値参照型の右辺値を渡した場合に T&& が T& に変換される。

POD 型に対する move 操作ってどうなんの?

struct X { int value; } x;
X x2 = std::move(x);
x.value; // これは OK? NG?

っていう疑問。
実質コピーするだろうから問題ないんだろうけど、規格で OK なのかどうかがよくわかんない。けど多分後で調べない。