実践C++応用講座こんにちは。今回まで可変長引数テンプレートのパラメータ・パックの加工方法の解説を続けます。「std::forwardを使うケースが多いのでそれも書いた方が良いのでは?」との意見を頂きましたのでこれと、テプレート・パラメータ・パックの複雑な加工をする方法例を紹介します。今回はあまり難しいことはやっていませんので復習がてら気楽に御覧ください。前回、const&を追加して、パラメータ受け取りの際に無駄なコピーをしないようにしました。今回は前々回の簡易tupleのようにコンストラクタで受け取ってメンバ変数に設定する場合を考えてみます。簡易tupleもそうですが多くの場合、このようなケースのメンバ変数は普通の変数として、初期化後も変更できるようにしたい場合がほとんどです。このような場合、コンストラクタでconst参照で各要素の初期値を受け取って各対応するメンバへコピーします。コピーではなくてムーブしたい時もありますね。const参照で受け取ると、実引数を修正できないためムーブできません。そこで、T&&で受け取るとムーブでき、ムーブしたくない時は参照で受け取れるようになります。因みに、このような使い方のT&&をユニバーサル参照と呼ぶそうです。型推論時に左辺値参照と右辺値参照のどちらにでも対応できるという意味です。付加するものも(私としては意外だったのですが)定番のようです。言われてみればSTLで良く見かけたような気がします。std::vector<T>などのコンテナ群のemplaceシリーズや、std::threadのコンストラクタなど。そこで、前々回の簡易tupleを修正し、ムーブ・コンストラクトに対応してみました。部分特殊化のコンストラクタのみの修正となります。側が前々回のコンストラクタです。それに対して今回はメンバ関数テンプレートのコンストラクタに変わってますね。iRest)で良いような気がしますが、実はダメなのです。テンプレートでない場合、このコンストラクタにとってtFirstはクラス・テンプレートのtupleを実体化する際に既に明示的に指定されています。型推論が機能する余地がありません。従って、tFirst&&は右辺値参照固定になってしまうのです。これをユニバーサル参照にするためには、コンストラクタを実体化する際に型推論を機能させる必要があります。そのためにコンストラクタをテンプレートとして定義しています。Wandboxで確認する。main()関数の最後でムーブしています。"\n";最後のstd::string型の要素についてムーブしています。aStringを”hello”で初期化しています。これをaTuple2へstd::moveにて与えたため右辺値参照で渡され、tuple内でstd::string型である最後のmMemberに渡す際に右辺値参照で渡されます。std::stringはムーブ・コンストラクタを持っているため、中身がムーブされるのでaStringの中身が空になりました。const&で受け取るとstd::moveで渡してもコピーになりますからaStringの中身が残ります。このユニバーサル参照のコンストラクタがあれば十分な気がするのですが、本物のstd::tupleはconst参照とユニバーサル参照の両方のコンストラクタを持っています。ムーブできない要素に右辺値を渡したら、その要素はムーブできないのでコピーされます。その時、全ての要素をムーブせずにコピーするために(const参照コンストラクタが呼ばれる)、両方用意しているようです。何か決定的な意味がありそうですが、良く分かりません。心当たりの方がいらっしゃいましたら、是非教えて頂けると幸いです。2.少し複雑なテンプレート・パラメータ・パックの加工2-1.その前に加工内容の説明int型等の基本型の多くはCPUのレジエスタ1つに収まるものが多いです。そのようなものを関数のパラメータで渡す場合、参照渡しより値渡しした方が一般には効率が良いです。呼び出された関数が、呼び出した側の実引数の配置されているアドレスを把握する必要があるので、実引数のアドレスを渡して、そのアドレス経由で間接的に引数をアクセスします。関数呼び出し時に、その関数が管理する領域へ実引数の値がコピーされます。コンパイラはそのコピーした領域を直接アクセスするコードを生成できます。そこで、型に応じてconst参照と型そのものを切り替えるヘルパーを作ってみました。PCではレジスタ1つに入る場合が多いスカラー型を値渡し、それ以外をconst参照渡しとしました。スカラー型(scalar):型の分類については、この表が判りやすいです。TypeHelperImpl<T>::type;型推論で使えると良いのですが、複雑な型は型推論できないため、下記のような使い方は意味がありません。(明示的に型を指定すれば使えますが、当然型推論されませんので意味が無いのです。"\n";}そこで、明示的に型指定して使います。world!!ところで、ここまで書いておいて何ですが、このTypeHelperはそれ程有用なものではありません。このような処理は原則として最適化に任せておいた方が良いと思います。複雑なテンプレート・パラメータ・パック加工のサンプル用と考えて下さい。String型について上記サンプルでは、Stringというクラス(struct)をstd::stringを単純に継承して作っています。コンストラクタも継承コンストラクタを使ってますので、単純に同じものです。std::stringの型名がとても長くて判りにくいので用意しました。Stringはただそれだけのものです。5.4.0のC++11におけるstd::stringの型名は次の通りです。>2-2.では本番ですと言っても簡単です。上記のように明示的に使うのは面倒ですから、一度関数テンプレートで中継して、その中で明示的な指定を行います。その明示的指定する時に、テンプレート・パラメータ・パックを加工します。const&や&&の付加は型推論と併用できました。型推論できない複雑な場合は、このように明示的に指定して加工することができます。前回のテンプレート・パラメータ・パックの加工で使ったprintLogで使ってみます。printLogImpl<TypeHelper<T>...>(t...);}このように一度中継することで、プログラマがいちいち明示的に指定しなくても済みます。後、ポイントとしては、パラメータのないprintLogImpl()関数がテンプレートになっていることです。TypeHelperで求めた型をprintLogImpl<tRest...>(rest...);にて中継しているのですが、ここで明示的に型を指定しているため、最後にtRestが空になった時も空のテンプレート・パラメータ・パックを受け取るprintLogImpl()が必要なのです。そこでそれが可能な形式で定義しています。クラスも渡してみたいので上で定義したString型を使いました。const&>]スカラー型は値渡しとなり、クラスはconst参照渡しとなりました。Wandboxで確認する。3.まとめちょっとひっぱり過ぎた感もありまが、今回でパラメータ・パックの展開は終了します。詳しい話は、前回リンクしたいなむのみたまさんのQiitaの記事を参考にされて下さい。ネタも尽きつつあり、次回は何をするかまだ決めていません。以降は私が有用性を理解できている機能を順不同で紹介して行きたいと思います。(有用だけど有用性を理解できていないものも、結構あります。それらは把握できたタイミングで紹介させて頂きますね。01:06出来る限りconstexpr指定もnoexpcept指定もした方がいいですよ。C++で見て意味を把握はしているのですが、必要性をあまり感じたことがないので理解が浅いです。constexprも同様です。コンパイル時処理の追求が甘いですね。noexceptは指定ミスを静的チェックしてくれないからリスキーなので必要時のみ使う方針です。(バグるとログが残らなくなるって致命的。バグの撲滅は不可能ですから、ログを取っていないプログラムの信頼性は悲惨と考えています。)https://wandbox.org/permlink/hWLr7Z1MJiHdkx4V今のところ、これらについて当ブログで扱う予定がないのですよ。explicitとconstexprは解説出来るほど理解が追いついていないと言うのが辛いところです。noexceptは有用な場面が限定的と考えているので取り扱わないだろうと思います。std::vectorに食わせるリソース管理クラスの話をした方が良いと感じたら解説するかもです。(ムーブ時の例外拒否機能としては有用と考えています。std::unique_ptrを使っておけばまず問題ないですし。05:17速度を犠牲にしてまで安全性を担保したいなら、もはやC++を書くべきではないでしょう。[要出典]>noexceptは有用な場面が限定的ありえないです。速度がC++のほとんど唯一の強みです。例外テーブルを消し去り、プログラムの速度を向上させることができるnoexceptは常に有用です。QoIのためには当然書くべきだと考えています。関数内で呼ばれる関数がnoexcept指定されているかどうかでnoexceptを切り替える事ができます。noexcept(式)と書けば式が無例外保証付きで呼べるかがbool値で返ります。と二重のnoexceptを書きます。https://wandbox.org/permlink/Uso1qLZH0PPvIznaconstexprはコンパル時処理が可能であれば、ユーザーがコンパイル時処理を選択するという選択の余地を与えます。コンパイル時処理不可能でないなら常にconstpexrをつけるべきであると考えます。今回の例はtupleであったので流石にconstexprは必要かなと思いました。05:35あちゃ~、すいません。速度がC++のほとんど唯一の強みです。速度を犠牲にしてまで安全性を担保したいなら、もはやC++を書くべきではないでしょう。[要出典]以前、gccとmsvcで測ったことがあるのですが、printf()とstd::coutではprintf()の方が高速でした。std::cout肯定派も少なくないと思います。)そもそも、もし、速度だけを追求するならアセンブラが最強です。メタ・プログラミングは生産性を無視するなら、手で事前に処理してハードコーディングすれば最強です。C++は速度だけでなく生産性も追求する言語なのです。C++はクラスやテンプレート、メタ・プログラミング等により生産性と速度を高いレベルでバランスする非常に優れた言語と考えています。(ただし学習コストがたいへん大きいのが巨大な難点)さて、noexcept(noexcept(bar()))の方法で呼び出している関数全てを指定するのは現実的ではないと思います。関数テンプレートの指定は憂鬱ですね。オーバーロード関数を全て指定するのも手間ですね。呼び出す関数が追加される度に指定を追加しないといけません。呼び出す関数のシグニチャが変わった時も同様です。指定漏れし、かつ、その指定漏れした関数が例外を投げるとそれはログに残らないのです。noexceptを書いてなければログに残りバグ解析できるのにです。指定は慎重に慎重に行う必要があります。そこまでの注意力を消費し、工数をかけることを正当化出来るほどの効果が期待できるのでしょうか?呼び出している関数のどれか1つでもnoexceptが指定されていなければ、これらの苦労は水の泡です。例外を投げない限り大きく速度が低下するわけではありませんので、例外を一切投げないからと言って大きな速度アップが期待できる筈もないです。従って、生産性と速度のバランス的にnoexceptを使うのが妥当な場面は限定的と考えます。(とはいえ、標準ライブラリが積極的に使うことには大賛成です。非常に使われる機会が多いものですし、仕様変更は頻繁にはありませんから、上記工数は余裕で正当化できると思います。今回の例はtupleであったので流石にconstexprは必要かなと思いました。多くのC++コンパイラは優秀なのでconstexprが無くても可能な場合はコンパイル時に処理されるケースが多いと思います。constexprを付けることでそれ以上の効果が期待できる状況もあるだろうと予想していますが、その状況を把握できていないのですよ。今回はtupleそのものの解説というわけではなくて、パラメータ・パックの展開方法の解説が目的ですのでconstexprに触れる必要はないかなと考えています。13:54>常に有用すいません嘘でした。限られた場合に有用で特にムーブやswapでの強い例外保証では必須。それ以外では例外処理を書いたほうが圧倒的コスト安。>std::coutとprintfiostreamは擁護できないほどクソなのでprintf肯定派です。(iostream否定派ではない)。>速度だけを追求するならアセンブラが最強です嬉しいことにそろそろ人間がコンパイラにまさるアセンブラを書ける時代は終わりそうです。>onstexprが無くても可能な場合はコンパイル時に処理されるケースが多い精々が整数くらいでは?constexpr関数なら実数も扱えます。実際、constexpr関数でコンパイル時にハッシュテーブルを生成することで実行時の処理を大幅に減らしたというプログラマもいましたし、有用。iostreamは擁護できないほどクソなので確かに問題点多いですよね。(私は特にlocaleが嫌いです。書式指定が面倒なのも嫌いです。constexpr関数なら実数も扱えます。その辺の情報収集が追いついてないのです。constexpr関数でコンパイル時にハッシュテーブルを生成する私が自力でやる時は、C++で普通の構文でハッシュテーブルを生成するプログラムを書いて、CMakeでプリ処理させそうです。constexpr関数で素直な記述で作れるなら、その方が生産性が上がるので絶対好ましいです。問題は私にとってそんな処理が必要になったのは大昔(15年近く前)なので、学習する機会が訪れていないのです。機会が来たらトライしてみます。コメントをキャンセルメールアドレスが公開されることはありません。テクノロジーの登録商標です。technology.。