雑草わんだほー

That's so wonderful♪

C++11のムーブは関数呼び出しのたびに明示する必要がある

先日、C++11のムーブセマンティクスを知って、パフォーマンスセルフチューニング狂のC++らしい新機能だと、少し感動していたりしていました。
それで利用を強いられている手元の古いコンパイラでも対応しているを確かめて、早速うっきうっきでコードを書きはじめたら、そこ右辺値参照効いてないよって突っこまれたので、実験してみたところ関数を通した型の扱いが普通の左辺値参照とは違うのですね。ポインタ渡しも含めて比較してみると、こういうこと(のはず)です。

ポインタ渡し 左辺値参照渡し 右辺値参照渡し
関数に渡す変数 T* T T&&
関数の引数宣言 T* T& T&&
関数内の扱い  T* T T


わぁややこしい。
手元で実験したなんちゃってコードはこちら。

#include<iostream>
#include<utility>

class foo {
public:
    void operator=(const foo& other) {
        std::cout << "lvalue" << std::endl;
    }
    void operator=(foo&& other) {
        std::cout << "rvalue" << std::endl;    
    }
};

static void func(foo&& arg){
    foo temp;
    temp = arg; // move するためには temp = std::move(arg) とfoo&&に再変換しなくてはならない
}

int main () {
    foo test;
    func(std::move(test)); // 標準出力にrvalueを出力するつもりがlvalueが出力されてしまう
    
    return 0;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ


Wikipediaにも解説がありました。

安全上の理由から、右辺値参照として宣言された名前つきの変数をそのまま右辺値として関数に渡すことはできない(そのような変数は左辺値となる)。std::move() を明示的に呼び出すことで、この制限を回避できる。

bool is_r_value(int&&) { return true; }
bool is_r_value(const int&) { return false; }
 
void test(int&& i)
{
  is_r_value(i); // false
  is_r_value(std::move(i)); // true
}
C++11 - Wikipedia


ええと、C++のマルチパラダイムな貪欲さ自体は素晴しいと思うんですけどね。文法の美しさだけが致命的に(以下略
ムーブセマンティクスなにそれ美味しいの?な方には以下のリンクを順に読んでいくことをお勧めします。

C++0x 標準ライブラリ完全解説 〜 No.02 std::move, <utility> - 野良C++erの雑記帳
move semanticsについて - joynote break;
std::vector::operator= - cppreference.com