LoginSignup
3
2

More than 5 years have passed since last update.

型推論解説1 – C++11であっても参照の参照は作れない(Reference Collapsingについて)

Posted at

注意: この記事は、2015年5月に公開したものを再構成したものです。

内容は2015年当時のままなのでご注意ください。今後、最新の情報に更新した記事を私のサイトで公開予定です。

http://ornew.net/


これから数回に分けて、C++の型推論に関わる記事を書くことにした。これは以前とあるコミュニティで出したクイズの解説に使った記事を書き直したものである。自身の再理解と、情報の整理を目的としている。予定は以下の通りである。

  1. (前提)reference collapsingについて
  2. Template Argument Deductionについて
  3. Forwarding Referenceについて
  4. autoについて
  5. (おまけ1)std::forwardについて
  6. (おまけ2)decltypeについて

この記事は1つ目のReference Collapsingについて解説する。C++11以降の型推論と密接な関係にある機能なため、型推論を語るには前提として正しい理解が必要となる。尚、Perfect Forwarding(完全転送)に関しては今回は書く予定はない。また機会があれば書くかもしれない。

各記事では執筆現在最新の草案N4296からの引用をする。C++11、C++14、C++1z(C++17)で追加された文面はその旨を併記する。当面は問題ないが、各文面は将来的に変更される可能性がある。型推論の機能はC++11以前から存在し、非常に歴史が長いため、大きな変更がされる事はそうそうないとは思うが。ちなみに、1つだけC++11に追加された文面をC++17で破壊的変更の予定であるとの事だ。それについては今後の記事で詳しく取り上げる予定である。

Reference Collapsingとは

本題に入ろう。Reference CollapsingはC++11から導入された機能である。それ以前の規格では存在しない。

「参照の参照を作れる機能」という記述をインターネットで稀に見かける。これは厳密には間違いである。実際は「参照の参照が作られた時、正しい参照に変える」機能である。確かに見た挙動的には参照の参照を作っているようにも見える。しかし参照の参照という型は従来通り作ることはできない。

§ 8.3.2/5

There shall be no references to references, no arrays of references, and no pointers to references.

参照の参照、参照の配列、参照へのポインタは、C++11以降でも禁止されているのである。そもそも参照の参照は存在する必要がない。参照と、参照への参照は同一視出来るからだ。

以下のコードを見てほしい。

int a;
typedef int& IntegerReference;
IntegerReference& b = a;

このコードはC++11に対応していないコンパイラはエラーを発生させるだろう。なぜならIntegerReferenceへの参照を作ると、それは参照の参照になってしまうからだ。先述したとおり参照への参照は禁止されている。しかしC++11ではコンパイルエラーにはならない。なぜだろうか。これこそReference Collapsingと呼ばれる機能であり、本記事の主題である。

このコードを見ると確かに「参照の参照を作っている」と思うのもわかる。しかし違うのである。ここでbの型は、int&になる。ただのintの参照である。Reference collapsingは、特定の条件下で参照の参照が生成された時に、正しい参照へと変換する機能のことである。

この変換が発生する条件は、以下の3種類の型に対して参照の参照が発生した場合である。

  • typedef
  • テンプレート引数
  • decltype
§ 8.3.2/6

If a typedef (7.1.3), a type template-parameter (14.3.1), or a decltype-specifier (7.1.6.2) denotes a type TR
that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type
“lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR.

つまり、評価されるまで型がわからない状況である。

文面で見るとわかりづらいので、文字ではなく記号で表してみよう。

T& + & → T&
T& + && → T&
T&& + & → T&
T&& + && → T&&

T型へのlvalue reference(T&)またはT型へのrvalue reference(T&&)がある時、それらに対してlvalue reference(&)またはrvalue reference(&&)が付与された場合、結果の型はどうなるかの対応表である。

見ての通り、rvalue referenceにrvalue referenceが付与された場合のみrvalue referenceが生成され、それ以外は全てlvalue referenceへと変換される。

この機能が必要とされる理由が、もしかしたらわからないかもしれない。コンパイルエラーになる方が素直に感じるかもしれない。しかし、以下のコードを考えてみて欲しい。

template< typename T >
void foo( T& ){}

int a = 0;
int& b = a;
foo<int&>(b);

ありふれたコードに見えるが、C++03だとエラー、C++11だと動く。理由の説明はもう不要だろう。これは非常に厄介である。この例は意図的に書いたので不自然に感じるかもしれない。しかし複雑なメタプログラミングをしていると、意図せぬところでこういう状況が起きてしまうことが多々あるのだ。

もし上記例のfooを、C++03で参照型を許可するように書き換えるなら以下のようになる。

#include <boost/type_traits/add_reference.hpp>
template< typename T >
void foo( typename boost::add_reference<T>::type ){}

非常に面倒くさいコードになった。boostを使っているところは一応自分でadd_reference相当のものを書くことはできる。面倒くさいのも問題である。そしてそれ以上に問題なのはArgument Deductionが使えないということだ。

メタプログラミングは今やC++に無くてはならない存在である。標準ライブラリの実装はメタプログラミングのオンパレードであるし、そもそも標準ライブラリにメタプログラミングを補助するType Traitsまで整備されている。そういった状況下において、Reference Collapsingは非常に便利で強力な機能なのである。

Reference Collapsingの解説はここまでである。これは型推論を説明する上で必須の知識となるため、前座として書いた。今後の記事はReference Collapsingを理解している前提となる。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2