実践C++応用講座こんにちは。今回はパラメータ・パックの展開方法についてもう少し解説します。1つはパラメータ・パックを加工して展開できるお話、もう一つは再帰を使わないで関数パラメータ・パックを展開する方法です。後者を使えるケースでは仕組みを把握すれば再帰よりはメンテナンスしやすいように感じます。(再帰はちょっと難しいですし。)1.パラメータ・パックの加工についてまずは、前々回の2-1.パラメータ・パックで説明した...の付け方のルールの2.と3.の補足です。サンプル・ソースも第14回のものを使いますが、解説しやすいよう少し修正しています。"\n";を追加しました。2パラメータ名をちゃんと付けました。three1-1.テンプレート・パラメータ・パックの加工ルールは簡単です。テンプレート・パラメータ・パック名に加工を施して、後ろに...を付けるだけです。定番の加工としては、const&を追加するものです。パラメータをconst参照で受け取れば、一時オブジェクト、変数の両方をコピーせずに受け取れます。第14回のサンプルはそれを考慮していませんので、実はパラメータをコピーして受け取っています。int型等の場合はコピーした方が効率が良いので問題ないですが、クラスのインスタンスでサイズが大きいような場合はコピーは頂けません。このようなときのためにconst参照を使います。追加し、その後ろに...を付けています。"three");}結果は同じなので見た目では何が変わったか分かりませんね。そこで、いつものTYPENAMEマクロで型を表示してみます。その際、第11回で説明したように単にTYPENAMEマクロに渡すとtypeid()の効果のため、constや&が消えてしまいますので、ダミーのクラス・テンプレート(Type<tType>)を介します。[6]>]因みにconst&を追加しなかった場合の実行結果は次のようになります。const参照になっていませんのでコピーが発生しています。(とはいえ、int型やポインタ型のコピーは問題ないです。大きなクラスなどを渡した時にコピーコストが気になります。[6]になっていることにちょっと驚くかも知れません。”three”はchar型の配列です。文字数が5文字でNULL終端なので6文字の配列となります。型(&)[要素数]となります。(&)[6]”となります。const*となります。文字列自体はコピーしていないのでコピーコストが無視できてしまいますが、もし、std::stringを渡してコピーするとコピーコストが気になります。(ヒープメモリ獲得コストもかかりますし。)Wandboxで確認する。1-2.関数パラメータ・パックの加工ルールはテンプレート・パラメータ・パックの加工と同じです。関数パラメータ・パック名に加工を施して、後ろに...を付けるだけです。この加工はびっくり高度な加工ができます。関数呼び出しもできます。サンプルを関数呼び出しで処理してみましょう。"three");}この結果はWandboxのgccでは次のようになりました。const&>]Wandboxで確認する。期待したのとは順序が逆ですが、渡したパラメータ全てに対してprint()関数が呼ばれています。printLogImpl()はprintLog()と同じ形式なので、いくらでもパラメータを渡せます。そこに、関数パラメータ・パックを加工して渡しています。"three");呼び出しは、コンパイラが次のように展開し、全てのパラメータが処理されます。"]\n");しかし、逆になってしまうのは何故でしょう?関数呼び出しのパラメータの評価順序は規定されていません。コンパイラを書いた人が自由に決めることができます。パラメータはスタックに積まれる処理系が多いのですが、その際、後(右)から順に積んでいくと、最初のパラメータがアドレスが一番若いところに積まれるので処理しやすいです。そのため、後から順に積む処理系が多いです。スタックに積む直前に処理すると都合が良いのです。その結果、関数に渡すパラメータが後から評価される処理系が少なくありません。ところで、よく見ると再帰がなくなってますね。関数パラメータ・パックを展開する際に関数呼出しするように加工すれば、渡された全てのパラメータを処理できるわけです。なんだ再帰しなくても処理できるのですね。2.再帰を使わないで関数パラメータ・パックを展開する方法1-2.の関数呼び出しで展開すると処理順序が処理系に依存してしまい、多くの処理系では逆順に処理されてしまいます。できれば、処理系に依存せず頭から順番に処理したいです。ネットを探しているとありました!!C++のパラメータパック基礎&パック展開テクニックの記事によるとSwallowと呼ばれるテクニック(イデオム?)を使うとお手軽かつスマートに展開できます。この記事では、std::initializer_listを使ってますが、私としては配列を初期化する方法をお勧めします。追加のインクルード不要で、必要な部分だけに記述すれば良いのでお手軽ですし、記述量も少ないですから見通しも良いです。