実践C++応用講座こんにちは。可変長引数テンプレートとして関数テンプレートが良く使われるので、前回は関数テンプレートで解説しました。クラス・テンプレートも可変長引数テンプレートとして使えますので、今回はstd::tupleを使って可変長引数のクラス・テンプレートを解説します。1.std::tupleとはこれもC++11でSTLに新たに加わったクラス・テンプレートです。実は私もあまり使ったことはないのですが、考え方としてはメンバ変数に名前を付けずに済むのでお手軽に使える構造体のようなものです。int型とshort型とstd::string型の場合、次のようにして定義します。(比較のため構造体の場合も書いてみました。---size=40123456helloworld!!このように、構造体との大きな相違は、メンバ変数に名前が付いていないことです。そのため、メンバ変数にはグローバルな関数テンプレートstd::get<index>()を使ってアクセスします。インデックス番号でアクセスするということはforループでアクセスできることを期待する人もいるかもしれません。インデックス番号はテンプレート・パラメータで与えます。テンプレート・パラメータはコンパイル時に決まっている必要があるので、forループで作ったものは使えません。<<で呼び出すoperator<<は型によってオーバーロードされた異なる関数を呼ぶ必要があります。上記コードを動作させるためには、ループの度に異なる実引数をスタックに積み、異なる関数を呼ばないと行けないので、同じ機械語コードでは対応できないのです。このようにメンバ・アクセスが面倒なので「お手軽な構造体」としてのstd::tupleは、あまり便利なものではないと個人的には感じます。メンバ変数に名前でアクセスできないのでスコープの広い変数に使うのは如何なものかと感じます。これだけならあまり使わないですが、実は結構便利な機能がありますので、後述します。2.簡易tupleを作ってみるなんかかなり話がそれてしまいました。今回の本題は可変長引数のクラス・テンプレートの展開方法の例示ですので、本題に戻ります。std::tupleは任意の数の任意の型のデータを保持できます。それを実現するために、可変長引数のクラス・テンプレートで定義されています。tuple;この記事を書くにあたって、実際のstd::tupleの定義をざっと見てみました。VC++)によって異なりますし、予想外に複雑でした。ここでは可変長引数のクラス・テンプレートの定義例として説明しますので、お手軽構造体の機能だけ抜き出し、かつ、できるだけ簡単に書いてみます。(std::tupleになるべく近づけたつもりですが、それなりに異なる筈です。constなtupleやムーブなどなど考慮していませんし。}};プライマリー・テンプレートはテンプレート・パラメータ・パックtTypesが空の時に使われます。部分特殊化テンプレートはそれ以外の時に使われ、先頭の型をmMember変数として定義し、残りにてtuple型を実体化して基底クラスにしています。これにより、テンプレート・パラメータ・パックを再帰的に処理してmMemberを次々と定義しています。基底クラスで次々とmMemberを定義するため、派生クラスでは覆い隠されて見えなくなります。いえどうせprivateなので外からはアクセスできません。当たり前ですがget<index>()も定義しないと何の役にも立たないです。プライマリー・テンプレート(形だけ。tTypes...>::get(iTuple);}2018年2月2日:いなむのみたまさんに頂いたコメントからヒントを得て修正しています。元はstd::conditionalを使っていたため、条件が成立しない方のtypeも実体化されます。typeがvoidとして実体化されます。void&はC++では許されていないため、エラーになります。修正前のサンプルプログラムでは、この対処のため参照ではなくポインタを使いました。その結果、参照のtupleでエラーになってしまったようです。テンプレートの奥は深いです。修正前のプログラムをWandboxで見てみる。2-1.get()関数テンプレートのテンプレート・パラメータについてテンプレート・パラメータとして欲しいメンバのインデックス番号と、ターゲットのタプルのテンプレート・パラメータ・パックを受け取っています。関数パラメータとして、そのテンプレート・パラメータ・パックで実体化されるtupleへの参照を受け取ります。都合の良いことに、このtupleを渡すとテンプレート・パラメータ・パックを型推論してくれます。ありがたい仕組みです。2-2.get()関数テンプレートの戻り値の型について戻り値の型を決めるために、get_helperクラス・テンプレートを使ってます。自身のtypeをtypedefしています。してtypedefします。してtypedefされます。decltype(auto)にて更に簡潔に記述できます。)なお、指定要素への代入もできるようにするため、要素への参照を返しています。値を1つ減らす度に、元のパラメータ・リストから先頭の要素を取り除いたパラメータ・リストで実体化されます。パラメータ・リストと同じです。N==0になった時、mMemberを返します。それが次々と呼び出し元へ返却され、最終的に元のget()関数テンプレートが返却します。Wandboxで確認する。(いなむのみたまさんの参照のtuple例も追加しています。)3.std::tupleの便利な機能std::tupleには実はかなり強力な機能があります。(簡易版の上記tupleは実装していません。)複数の値を使って大小比較したい時って時々あります。プログラムしていて良くでてくるのがバージョン番号の比較ですね。「2.3.5以上なら◯◯し、そうでないなら△△する」みたいな時です。maintenance;};のように定義することもあるでしょう。この時、「2.3.5以上なら」という条件判断は意外に面倒です。}こんな時、std::tupleを使うと楽できます。std::tupleには比較演算子がオーバーロードされてます。これは、上記のような面倒な段階的な比較をoperator<だけを使って行ってくれます。std::tupleの全てのメンバの型についてoperator<が定義されていたら、適切に比較処理してくれます。}}これはVersionの型をstd::tupleへ変更してしまいましたが、構造体のままでも有用です。Versionは単なる構造体ですから、大小比較するためには、自分で比較演算子を定義する必要があります。std::tieを使って元の構造体の各メンバへの参照を持つstd::tuple化することで、直感的で実にスマートに定義できます。short&>型を返します。同じstd::tuple<>同士には比較演算子が定義されているので簡単に再定義できるわけです。Wandboxで確認する。std::tupleですから、当然、各メンバの型は異なっていても構いません。実はこの機能は、yohhoyの日記のtieを用いた比較演算子の実装をみて知りました。詳しいことはこのyohhoyさんの日記を参照ください。yohhyさんはC++の標準規格について本当に詳しい方です。4.まとめ今回は、std::tupleを例にとって、可変長引数のクラス・テンプレートのテンプレート・パラメータ・パックの展開例を解説してみました。関数テンプレートは関数を作りますが、関数は実行時に処理時間を消費し、かつ、記憶領域も必要とする「一時オブジェクト」や「変数」を生成したい時に使うものですね。クラス・テンプレートは「型」を生成する時に使います。「型」自体はコンパイラへの命令に過ぎず、メモリが割り当てられません。「型」のインスタンスを作る時に初めてメモリが割り当てられるのです。インスタンスを作らない限り、実行時に処理時間やメモリを全く消費しません。私も明確な言葉で説明出来るほどは理解できていないのですが、これらの間には意外に本質的な差異があるようです。この辺の情報処理の本質的な「何か」に触れることができるのが、C++の大きな特長ではないかと感じる今日此の頃です。次回も可変長引数テンプレートの話題を追いかけます。テンプレート・パラメータ・パックの展開方法や加工方法についてもう少し解説してみます。