c++boost.gif (8819 bytes)Type-safe 'printf-like' format class

Choices made

"Le pourquoi du comment" ( - "どうしてそうなの?")


The syntax of the format-string

format は新しいライブラリだ。そのゴールの一つは、 printf の代替物を提供することにある。つまり、 format は printf 用に設計された書式文字列を構文解析することができて、与えられた引数にその書式を適用して printf と同じ結果を生成できる。
この制限の下で、書式文字列の文法には大雑把に3つの選択肢が有り得た :

  1. printf とまったく同じ文法を用いる。これは多くの経験のあるユーザに知られているし、 ほとんどすべてのニーズにフィットする。しかし命令の終端を断定するために不可欠な型変換文字は、 C++ ストリームの文脈では、 ストリームの関連する書式化オプションをセットする程度の役にしか立たない(%x なら hexa をセットする、等...) このお仕着せの型変換文字は、意味付けを変更した上で、省略可能にするのが良いだろう。
  2. 互換性を維持しながら拡張された printf 文法。まだ printf の文法として有効でない文字や構造を用いる。例. : "%1%", "%[1]", "%|1$d|", .. 始端 / 終端マークを用いることで、あらゆる種類の拡張を考慮できるようになる。
  3. printf 互換のものと平行して、非レガシーモードを提供する。 既存の printf 文法との互換性という制約を受けずに、他の目的に適すように設計できる。
    しかし printf の文法の代替物(既存のものより明確に優れていて、かつパワフルなものになるだろう)の設計は、 format クラスの構築とはまた別の仕事だ。 そのような文法が設計されたときには、 Boost.format を二つのライブラリに分割することも考慮すべき だろう : 一方はこの新しい文法と歩調を合わせて開発され、もう一つはレガシーな文法を サポートする (おそらくは高速で、 snprintf やその同類に勝る安全面での改良が組み込まれたバージョンになるだろう)。
完全で、気の利いた、 printf よりも明確に C++ ストリームに適応した新しい文法が手元にないので、二つ目のアプローチを選択することにした。 Boost.format は printf の文法を用い、その文法を拡張することで拡張機能(桁送り、中寄せ)を表現することができる。
また、 printf の文法の弱点を克服するために、これまでのものに替わる互換表記を提供する :


なぜ関数呼び出しではなく演算子で引数を渡すのですか?


演算子による方法の不便さ(一部の人にとって)は、混乱させられることがあるということだ。 演算子をオーバーロードし過ぎると人々を真の混乱に陥れるという お決まりの警告だ。
format オブジェクトの仕様は限られた文脈(最も多いのは "cout << " の直後)になるだろうってことと、 引数がいかにも書式文字列に続いているように見えることから :
format(" %s at %s  with %s\n") % x % y % z;
人々をそれほど混乱させないだろうと期待できる。

演算子の別の恐怖は優先順位の問題だ。 format("%s") % (x+y) と書かずに format("%s") % x+y を書いた場合どうなるだろう?
これだとコンパイル時に問題が起きるので、エラーはすぐに検出されるだろう。
もちろん、この行は tmp = operator%( format("%s"), x) を呼び、
それから operator+(tmp, y) を呼ぶ。
暗黙の変換が定義されていない限り tmp は format オブジェクトとなるだろう。そのため operator+ の呼び出しは失敗する。 (もちろん、君がそんな演算子を定義した場合は除く)。 だから君は優先順位の間違いはコンパイルの際に知らされると安心して決め込んでいい。


その一方で、関数アプローチには本物の不便さがある。 多くのテンプレート関数を定義する必要があるんだ。こんな感じに :

template <class T1, class T2,  .., class TN> 
string format(string s,  const T1& x1, .... , const T1& xN);

そして N を 500 まで定義したとしても、 まだ C の printf にはない上限を設けることになる。
それに、 format はどうにかして printf をエミュレートできる場合もあるけど、 printf の完全な等価物には程遠い。根本的に異なる外見を用いる方がベストだ。そして演算子呼び出しを使うのは、その点ではとても成功している!


いずれにせよ、もし僕らが実際にフォーマルな関数呼び出しテンプレートの仕組みを選択していたら、

operator<< ( stream,   const T&)
が与えられているクラス T しか表示することができなかっただろう。 なぜなら、 const と 非 const の両方を許容すると組み合わせ爆発が生じるからだ - もし 10 個までの引数で行くにしても、 2^10 個の関数が必要になる。
(T& と const T& のオーバーロードを提供することは C++ 標準の不備の最先端だが、おかげでサポートの保証からは程遠い。しかし現在ではいくつかのコンパイラがそうしたオーバーロードをサポートしている)
非 const 版の等価物しか提供しないという悪い設計をすることもできるけど、それはユーザにまた別の根拠の無い制限を押し付けることになる。
また、マニピュレータのいくつかは関数なので、 const な参照として渡すことができない。 そのため関数呼び出しアプローチはマニピュレータを上手くサポートしない。

結論として、コンパイル時に引数の数を知ることができない場合には、専用の二項演算子を用いることが最もシンプルで、ロバストで、かつ制限の少ない引数渡しのメカニズムなんだ。


なぜ 'with(..)' のようなメンバ関数でなく operator% なんですか??

技術的には、
format(fstr) % x1 % x2 % x3;
は、
format(fstr).with( x1 ).with( x2 ).with( x3 );
と同じ構造をしている。後者には優先順位の問題は何も無い。 後者のただ一つの欠点は、演算子を用いるのに比べて、一見してこの行が何をしているのか 把握しづらいということだ。 .with(..) を呼び出すのは、コードのほかの行でやっていることと同じように見える。 だから、好みの問題だけど、この方がより良いな解決方法だろう。 余計な文字を用いる点と、'with(..)'を用いたコードの行の全般的に散らかった側面は、僕に真の演算子を選択させるのに十分だった。

なぜいつもの operator<< でなく operator% なんですか??


なぜ operator() や operator[] ではなく operator% なんですか??

operator() には、関数に引数を送る自然な方法であるというメリットがある。 また、 operator[] の意味が format で使うには上手く当てはまると考える人もいる。
技術的にはこれらは operator% と同じくらい良い選択だ。しかしすごく醜い。 (好みの問題だ)
それにそもそも、書式文字列の中の "%" で参照されている引数を operator% を使って渡すことは、それらの演算子を使うよりずっと自然に見える。


July 07, 2001

© Copyright Samuel Krempp 2001. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.

Japanese Translation Copyright © 2003 Kent.N
オリジナルの、及びこの著作権表示が全ての複製の中に現れる限り、この文書の複製、利用、変更、販売そして配布を認める。このドキュメントは「あるがまま」に提供されており、いかなる明示的、暗黙的保証も行わない。また、いかなる目的に対しても、その利用が適していることを関知しない。

このドキュメントの対象: Boost Version 1.29.0
最新版ドキュメント (英語)