const rvalue referenceは何に使えばいいのか

What are const rvalue references good for?

を適当に訳したものです。(微妙な表現や、誤訳があるかもしれません。変なところは原文と照らしあわせてお願いします。)
あまり使われることのないconst rvalue referenceの使用について触れている記事です。


あなたはこれまでconst rvalue reference (const T&&)を使用した実用的なコードを見たことがないかもしれない。それは特に驚くようなことではない。rvalue referenceの主な目的が、コピーの代わりにオブジェクトをムーブする為であるからだ。そして、オブジェクトの状態をムーブする事は変更を意味する。結果として、ムーブコンストラクタとムーブ代入演算子両者の関数の標準のシグネチャは、その引数としてnon-const rvalue referenceを取る。

しかし、const rvalue referenceはC++に存在し明確に定義された束縛規則を持っている。特に、const rvalueはconst lvalue referenceよりもconst rvalue referenceに優先的に束縛される。以下のコードが束縛の優先度を表している。

struct s {};
 
void f (      s&);  // #1
void f (const s&);  // #2
void f (      s&&); // #3
void f (const s&&); // #4
 
const s g ();
s x;
const s cx;
 
f (s ()); // rvalue        #3, #4, #2
f (g ()); // const rvalue  #4, #2
f (x);    // lvalue        #1, #2
f (cx);   // const lvalue  #2

対称性に注意して欲しい: const lvalue referenceはrvalueを束縛出来るが、const rvalue referenceはlvalueを束縛出来ない。 特に、これがconst lvalue referenceにconst rvalue referenceが出来る事の他にもあらゆる事を可能たらしめている。(すなわちlvalueの束縛)

これによってconst rvalue referenceはかなり役立たずにされている。考えて欲しい: 我々が必要とするすべてが変更不可能なオブジェクト(rvalue or lvalue)への参照であれば、const lvalue referenceは完璧に働く。そして、右辺値を選び出したい唯一のシチュエーションはムーブする場合だ。だが、このケースにおいては参照が変更可能である必要がある。

加えて、const rvalueはあまり意味がない。上記のg()のようにconst rvalueを返す関数を定義したり、std::move()をconstオブジェクトに対して呼ぶ事も出来るが、特に意味は無い。どうして、関数を呼び出す側が関数の戻り値のコピー対して出来る事に制限を課すんだ?どうして、変更不可能なオブジェクトをムーブしようとするんだ?

そこで、const rvalue referenceの唯一の使い道は、もしrvalue referenceを完全に使用不可にしたくて、それを無意味でも規格上で問題になる心配のない方法で行う必要がある場合だ。(原文 *1 )

適例はstd::reference_wrapperクラステンプレートとそれのref()とcref()ヘルパ関数だ。 reference_wrapperはlvalueの参照を保持する事だけを意味しており、標準においてref()はrvalueに使えず、rvalueの為にcref()が存在する。const rvalueでさえ呼ぶことが出来ないようにする為に、標準ではconst rvalue referenceを使っている:

template <class T> void ref (const T&&) = delete;
template <class T> void cref (const T&&) = delete;

ムーブセマンティクスをサポートする事だけでなく、rvalue referenceの構文はperfect forwardingにも使われている。しかしながら、T&&にconstを付けるとこのような機能をサポートする為の特別な引数の推論のルールを無効化出来る。例:

template <typename T> void f (T&&);
template <typename T> void g (const T&&);
 
s x;
 
f (s ()); // Ok, T = s
f (x);    // Ok, T = s&
 
g (s ()); // Ok, T = s
g (x);    // Error, binding lvalue to rvalue, T = s 

*1:So it seems the only use for const rvalue references is if you need to disable rvalue references altogether and you need to do it in a bullet-proof way that will deal with the above pointless but nevertheless legal cases.