実践C++応用講座こんにちは。暑い日が続いています。逆走台風まで来ています。私も夏バテでついダラダラしてしまいます。私は書式付き出力したい時にprintfのお手軽さが懐かしいです。書式指定を間違っても取り敢えず出力してくれるstd::coutの魅力も大きいです。そこで今回は、boost::formatを使ってこの両者を満たすお手軽printf関数を作ってみましたのでご紹介します。1.何故にC++版printf関数をわざわざ作る?std::cout(std::ostreamすべて同じです)も、実は書式付き出力をサポートしています。setw、setfill、setprecisionなどが良く使われると思います。頻繁に使う機能ではないので、いつもマニピュレータ名を忘れてしまって面倒な思いをします。その点、C言語のprintfはキーワードではなく記述場所で指定するので一度理解したら後はそうそう忘れることはありません。上記の点でprintfは優れているのですが、残念なことに型チェックが凄く甘いです。最近のコンパイラはprintf等の標準ライブラリについては書式と引数の型が一致していない時、警告してくれるため困ることはかなり減っているのですが、自作関数の書式指定の不一致警告はサポートされていないため、デバッグ・ログ出力で書式指定する独自関数を作った時に辛い思いをします。(やっと不具合を再現できてログをみていると、書式指定ミスでちゃんと読めないとか、お馬鹿なことを結構やりました。)こんな時はboost::formatの出番です。これを使うと、printf()ライクな書式指定で変数の値をよしなに出力してくれます。書式指定を間違ってもそれなりに出力されるのでデバッグ・ログのような用途では本当に有り難いです。問題が1点ありboost:formatが開発された時はまだ、可変長引数テンプレートがありませんでした。そのため、複数の変数を出力する時は%演算子で区切ります。(これにどうしても慣れません。していればよいのにとか思ってしまいます。123.456;更に、デバッグ・ログ用にマクロでラップしたくても容易にはできません。printf()なら簡単なのですが。while(0)#endifWandboxでみてみる。この2つのジレンマを解決するため、(時々アナウンスしていましたが)既にTheolizer®では対応しています。お手軽に導入できればと思うことが幾度かありましたので作ってみました。面倒なことはないです。boost::formatはヘッダ・オンリ(boostをビルドしなくても使える)ですので、ダウンロードして解凍すればOKです。このページから、お使いのOS用の最新版をダウンロードし、適切なところへ解凍して下さい。2-2.ubuntuの場合もっと簡単です。以下のコマンドでインストールされます。意外に簡単ですので、いきなりソースを示します。templates)の「2-2.よくある使い方」にて解説したものそのままですので見比べて見て下さい。iParams...);}Wandboxで確認する。ローカル環境でビルドする時は、インクルード・パスとして、boostを解凍したフォルダを指定して下さい。main.cppです。ubuntuでapt-getでインストールした場合は/usr/include/boost以下へインストールされますので、デフォルトで/usr/includeが指定されるため、別途インクルード・パスを指定する必要はないようです。ubuntuでは-std=c++11オプションをお忘れなく。4.boostはコンパイルが遅いので、明示的実体化で対処boostのヘッダ・オンリなライブラリは、#includeするだけで使えるのでお手軽なのですが、1つ欠点があります。いやマジで結構伸びます。小さなプロジェクト(コンパイル単位が少ない)で使う場合にはそれほど気になりませんが、大きなプロジェクトで使う時は結構困ります。そこで、boost::formatをインクルードするコンパイル単位を1つにしてしまいたいです。そのためには、以下の3つの機能を実装側(cpp)で記述すればよいです。実装側だけでマシン語へ落とせますので問題ありません。iFirstは呼び出し側で指定しますので、その型を実装側は知らないため、マシン語へ落とせません。このような使い方の場合、出力したい変数の多くは数値型(arithmetic)ですので、基本型すべてを明示的実体化することが可能です。実は、boost::formatはユーザ定義型にも対応しています。そのユーザ定義型を出力できるoperator<<があれば使えます。Wandboxで確認する。そこで、ユーザ定義型をクラス・テンプレートで受けて、プライマリー・テンプレートでユーザ定義型、数値型について部分特殊化して数値型のboost::format呼び出しすればboost::formatと同程度の機能に対応できます。Enum型もお手軽に出力したいものです。これはenum型について部分特殊化し、それをstatic_castしてboost::formatを呼び出せば良いです。以上を纏めると次のようになります。Wandboxで確認する。xprintf.hxprintf.cppmain.cppCMakeLists.txtCMakeList.txtを使ってプロジェクトを生成する時は、-DBOOST_ROOT=<boostを解凍したフォルダのパス>を指定して下さい。WindowsでC:\boost_1_67_0以下へ解凍された場合は、-DBOOST_ROOT=C:\boost_1_67_0です。ubuntuでapt-getを使ってインストールした場合は、これを指定しなくてもfind_packageがうまいこと見つけてくれるようです。make当ソフトウェアはご自由に使って下さい。ご自身の責任でお願いします。)5.最後に速度について残念ながら、boost::formatは速度がちょっと遅いです。書式指定の順序と出力する値の順序を一致させないでも良いという国際化対応時に非常に有り難い機能をサポートしているからです。(日本語と英語では単語の並び順が異なりますが、例えば、書式文字列を日本語→英語に変えた時、出力する値の順序を変更しないで済めば文字列だけ変えれば国際化できるのです。gettextという非常にメジャーな国際化対応方式で積極的に使われている仕組みです。記載があるので、速度が気になる場合には確認されて下さい。6.まとめ夏バテでなかなか集中できず、今回はちょっと駆け足になってしまいました。内容的には既に解説したことの比較的簡単な応用ですので、ご容赦下さい。それでは、台風の被害が大きくならないことを祈りつつ、今日はこのへんで終わりにいたします。18:13親近感わきます。なんか似たようなことしてますね。ログを取るのはプログラマの永遠の課題ですもんね。使えばテンプレート・パラメータ・パックを一発で展開できるんですね。12:41トモノリさん、コメントありがとうございます。調べてみたのですが、boost::formatは引数の数に制限なさそうです。last)とすると、「フォーマット文字列だけf」に対応できなくなります。(xprintf(“abc”);がエラーになる)避けけるものなら避けた方が好ましい制限かなと思います。書式文字列のみはちょっと怖い印象もありますが、boost::formatの仕様(書式内で必要とする引数の数と実際の引数の数が一致すること)ならば書式文字列攻撃もできませんので問題ないと思います。17:25私の最後の表現があいまいでした、可変引数なしは、単なる文字列で扱う特殊化の方が良いのかな?でした。01:46トモノリさんう~ん、私が勘違いしているかもしれないのですが、それではエラーになるようです。https://wandbox.org/permlink/KpYRQpg2ChrBQysOboost::formatクラスは、std::stringへのキャスト演算子を定義していないのだろうと思います。コメントをキャンセルメールアドレスが公開されることはありません。テクノロジーの登録商標です。technology.ブログ内サンプル・コードのライセンス1.本ソフトウェアは、ご自由にお使いください。2.本ソフトウェアは、あるがままの姿で提供いたします。明示黙示を問わず、瑕疵がないこと、特定の目的に適合すること、使用地域における法令の規制等に合致すること、その他一切の保証をいたしません。3.本ソフトウェアについては、無償で提供するものですので、何らかの不具合が発生した場合であっても、セオライドテクノロジーは、何らの責任も負いません。使用するかどうかのご判断も、ご自身でお願いいたします。テクノロジーは一切その責任を負いません。データのバックアップ等については、お客様の責任において行ってください。テクノロジーは一切その責任を負いません。テクノロジーは、一切の責任を負いません。7.セオライドテクノロジーは、本ソフトウェアに関する技術的サポートは行いません。