Universal Reference is 何

universal reference という文字が見えたけど聞いたことなかったのでまとめました。
知識としては皆さんご存知だと思いますので復習程度に見ていただければ幸いです。
知らなかった人はこの記事で入門しましょう!

Scott Meyers氏による universal reference の 定義

If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

あれ…この記事これでおしまいでいいんじゃ…?

&& の持つ意味

もう少しお付き合いください。

void f( std::vector<int>&& v ) {} // 1
template <typename T>             // 2
void f( T&& t ) {}

// some_class というクラスがあったとする
some_class sc1;
some_class&& sc2 = sc1;           // 3
auto&& sc3 = sc2;                 // 4

結論からいうと2、4の T&&, auto&& が universal reference にあたるわけですが、ちょっくら説明いたします。

"&&" を見たら無条件で「あっ!rvalue reference だ!」となる方もいらっしゃることと思いますが、早計です。落ち着きましょう。
ここで問題になってくるのが、Tという型が deduced type である場合、単純に "T&&" と書いただけではこれは rvalue reference にも lvalue reference にもなりうる、ということです。
簡単にいえば、

  • lvalue によって初期化が行われれば universal reference は lvalue reference になる
  • rvalue によって初期化が行われれば universal reference は rvalue reference になる

ということです。(lvalue やら rvalue やらわからない人は僕の前の記事や、C++の言語規格を参照してください)

というわけで、元のコードをもっかい見てみましょう。

int x = 0;
f( x ); // 1: x は lvalue なので、T&& は int& になる
f( 0 ); // 2: 0 は rvalue なので、T&& は int&& になる

// 4: sc2 は lvalue なので、some_class& sc3 = sc2; と同じ意味になる
auto&& sc3 = sc2;

もうお分かりですね?

気をつけるべき universal reference

このコードは universal reference でしょうか?

template <typename T>
void f( std::vector<T>&& v ) {}

答えは No. です。なぜなら "&&" が T にかかってないからです。

では、これはどうでしょう。

template <typename T>
void f( T const&& t ) {}

答えは No. です。const をつけたら universal reference の要件を満たさないようです。
なので T const&& は rvalue reference になります。

じゃあこれは…? vector<T>::push_back(T&& t) の T&& は universal reference でしょうか?

template <typename T/*, class Allocator = allocator<T>*/>
class vector
{
public:
    void push_back( T&& t );
};

答えは No. だそうです。なぜなら、vectorを使うときは vector<int> のように T の型がわかりますので、type deduction が発生しないからです。つまり、T&& は rvalue reference といえます。
(と言っていますが、vector<int&> にしたら T&& は lvalue reference となります。だから universal reference といってもいいんじゃないかと個人的には思っています。type deduction が発生しないので定義には当てはまらないですけどね。おそらくですが、

vector<int> v; // vector はさっき書いた vector
int i = 0;
v.push_back( i ); // error! i は lvalue reference

とエラーになって、T&& と書いているからといって lvalue も rvalue も渡せるわけではないということが言いたいのだと思います。)

最後にこいつはどうでしょう?

template <typename Args...>
void f( Args&&... args );

答えは Yes. です。No 続きだったから No だと思った方もおられるでしょうが、これは普通に Yes でいいと思います。

universal reference の注意点とか書きつつ universal reference だったの1つだけじゃねぇかとかいうツッコミは勘弁してください。

最後に

内容的には全く新しくないわけですが、復習にはなったかと思います。
Perfect forwarding(std::forward) とか有名ですが、それにつながる知識ですので知らなかった方は知っておいて損は無いと思います。