move semantics に関するメモ(2)

混乱の原因は std::forward()。


std::forward() の実装はいまのところこんな感じになっているらしい。

template <class T, class U,
    class = typename enable_if<
        (is_lvalue_reference<T>::value ?
            is_lvalue_reference<U>::value :
            true) &&
        is_convertible<typename remove_reference<U>::type*,
                       typename remove_reference<T>::type*>::value
    >::type>
inline
T&&
forward(U&& u)
{
    return static_cast<T&&>(u);
}

で、こいつは lvalue を渡したときは lvalue に、rvalue を渡したら rvalue にならないといけないらしいんだけど、

struct X { } x;
static_assert(std::is_rvalue_reference<decltype(std::forward<X>(x))>::value, "failed");

となって rvalue reference になってしまう。関数が rvalue reference を返す場合は rvalue になるのでダメ。
展開した後の関数を書くとこうなる。

inline X&& forward(X& u)
{
    return static_cast<X&&>(u);
}

テンプレート引数の U&& は自動的に推論されて && が消えて U が X& になるけど、T&& の部分はユーザが自分で X って書いてるから X&& にしかならない。


おかしいなーと思ってよく考えると、forward はテンプレートの文脈での転送目的にしか使わないので、自然と

template<class T, class A1>
T* create(A1&& a)
{
    return new T(std::forward<A1>(a));
}

って使い方になる。
で、これの場合、create に X 型の lvalue を渡した場合は A1 は X& になるので、

Hoge* create(X& a)
{
    return new Hoge(std::forward<X&>(a));
}

こうなる。
見事に X& ってなってて、関数が lvalue reference を返す場合は lvalue になるので、これで無事 Hoge に lvalue で渡すことができると。


つまりは std::forward() に渡される値が lvalue か rvalue かっていうのは全く関係なく、呼び出し元の関数で渡される値が lvalue か rvalue かというのが重要。
そこが分かってなくて単体で実験しようとして混乱していたみたい。単体で使うと自分で X& とかって書かない限り全て rvalue reference で返されちゃう。


あと関数が lvalue reference を返す場合は rvalue にならないっていうのも知らなかった。ほんと C++ 怖い。