Rokiのチラ裏

学生による学習のログ

std::forward

分からんと言われたので解説的なエントリ。
まずC++には、テンプレート引数をrvalue referenceとして引数に設定しlvalueを渡すと、lvalue referenceになるという仕様がある*1

#include<experimental/type_traits>
#include<iostream>
using namespace std::experimental;
struct X{};

template<class _Tp>
void f(_Tp&& x)
{
    std::cout<<std::boolalpha<<is_rvalue_reference_v<decltype(x)><<"¥t"<<
is_lvalue_reference_v<decltype(x)><<std::endl;
}

int main()
{
    X x;
    f(x); // false true
    f(X()); // true false
}

何故このような特殊な仕様があるかというと、rvalue referenceとlvalue referenceのどちらもを、一つの関数で済ませようという考えに準じているためである。
では、以上の前提知識を基に、std::forwardが必要となるシーンを示す。

#include<utility>
struct X{};

template<class _Tp>
void f(_Tp&& a)
{
    X x(std::move(a));
}

int main()
{
    X x;
    f(x);
    // もうxは使えない。
}

関数f内でこのように記述してしまうと、ユーザーはムーブされている事に気付く事ができない。
そしてそもそも、ムーブするか否かはユーザー側が指定する事だ。ユーザーがムーブの記述をしたから、ムーブの動作がその関数内で発動するべきである。それはつまりどのように成し遂げるかというと、lvalueをユーザーが指定した場合はムーブを行わないのでlvalueのまま、rvalueをユーザーが指定した場合(std::moveを付与している場合)はrvalueに出来れば、願いは叶う事となる。その願いを叶えた標準ライブラリが、std::forwardである。そしてその願いが叶えられた時、人々はそれをPerfect forwardingと呼ぶのである。

#include<utility>
struct X{};

template<class _Tp>
void f(_Tp&& a)
{
    _Tp x(std::forward<_Tp>(a));
}

int main()
{
    X x;
    f(x); // lvalue referenceとなりムーブは行われずコピーされる
    f(std::move(x)); // ムーブ指定(rvalue)なのでムーブされる
}

願いと言っても、forwardの本質はテンプレートメタプログラミングによる渡された型への規定チェックと仕様*1を利用したキャストである。

#include<experimental/type_traits>
#include<iostream>
#include<utility>
using namespace std::experimental;

struct X{};

template<class _Tp>
void f(_Tp&& x)
{
    std::cout<<std::boolalpha<<is_rvalue_reference_v<decltype(std::forward<_Tp>(x))><<"\t"<<
        is_lvalue_reference_v<decltype(std::forward<_Tp>(x))><<std::endl;
}

int main()
{
    X x;
    f(x); // false true
    f(std::move(x)); // true false
}

余談

Forwardとは関係なく、規格*1関連で。
gcc4.4.7とか、少しバージョンが低いコンパイラだと以下のようなコードが通るので注意が必要である。

int main()
{
    struct X{}x;
    X&& r=x; // ill-formed
    X&& r1=X();
}

これは、現在のドラフト内容に準じていない。当エントリの冒頭でアンダーラインを引いているように、テンプレート引数をrvalue referenceとして引数に設定しlvalueを渡さなければ、lvalue referenceにはならないので、該当箇所はill-formedのはずである。
なぜこのような事が起きているかというと、この辺りのバージョンリリースの時期にドラフトの文言が変更されたからである。

*1:§ 14.9.2.1 Deducing template arguments from a function call p3