某所でC言語のprintf
の書式指定って初学者にはけっこうめんどうだよね、というブログのエントリを見て。
たとえば。C言語だとこんな感じ。
#include <stdio.h> int main(int argc, char* argv[]) { const int n = 123; const double r = 123.45; const char s[] = "hoge"; printf("n = [%10d]\n", n); printf("n = [%-10d]\n", n); printf("r = [%10f]\n", r); printf("r = [%-10f]\n", r); printf("r = [%10.3f]\n", r); printf("r = [%-10.3f]\n", r); printf("s = [%10s]\n", s); printf("s = [%-10s]\n", s); return 0; }
実行結果。
n = [ 123] n = [123 ] r = [123.450000] r = [123.450000] r = [ 123.450] r = [123.450 ] s = [ hoge] s = [hoge ]
もっとも、C++のストリームで書式付きで出力するのはめんどうだ、という話のほうがよく聞きます。なのでC++でもprintf
を使っている場面を見かけます。
たとえば。C++だとこんな感じ。実行結果は上と同じなので省略。
#include <iostream> #include <iomanip> int main(int, char* []) { using namespace std; const int n = 123; const double r = 123.45; const char s[] = "hoge"; cout << "n = [" << right << setw(10) << n << "]" << endl; cout << "n = [" << left << setw(10) << n << "]" << endl; cout << "r = [" << right << setw(10) << r << "]" << endl; cout << "r = [" << left << setw(10) << r << "]" << endl; cout << fixed << setprecision(3); // 表示範囲を小数点以下3桁に固定 cout << "r = [" << right << setw(10) << r << "]" << endl; cout << "r = [" << left << setw(10) << r << "]" << endl; cout << "s = [" << right << setw(10) << s << "]" << endl; cout << "s = [" << left << setw(10) << s << "]" << endl; return 0; }
これをもう少し簡単にできないかと考えてみた。
書式指定の仕様を全部やると大変なので、ここでやるのは幅、小数点以下桁数、位置(右寄せ/左寄せ)だけですが。
マニピュレータを自作して解決してみる
オーソドックスにマニピュレータを自作。
setformat
関数でパラメータをForamt
のオブジェクトに保存し、そのオブジェクトがストリームに出力されるときに一度にパラメータを設定しています。右寄せ左寄せは幅の値の正負で判定しています。
#include <iostream> struct Format { const int width; const int precision; }; std::ostream& operator << (std::ostream& out, const Format& format) { if(format.precision > 0) { out << std::fixed; out.precision(format.precision); } if(format.width > 0) { out << std::right; out.width(format.width); } else { out << std::left; out.width(-format.width); } return out; } Format setformat(int width, int precision = 0) { Format format = { width, precision }; return format; } int main(int, char* []) { using namespace std; const int n = 123; const double r = 123.45; const char s[] = "hoge"; cout << "n = [" << setformat( 10) << n << "]" << endl; cout << "n = [" << setformat(-10) << n << "]" << endl; cout << "r = [" << setformat( 10) << r << "]" << endl; cout << "r = [" << setformat(-10) << r << "]" << endl; cout << "r = [" << setformat( 10, 3) << r << "]" << endl; cout << "r = [" << setformat(-10, 3) << r << "]" << endl; cout << "s = [" << setformat( 10) << s << "]" << endl; cout << "s = [" << setformat(-10) << s << "]" << endl; return 0; }
テンプレートにしてみる
printf
で指定される書式はリテラルで指定される場合がほとんど。上記でいえばマニピュレータに渡される値は定数になるということ。
ならテンプレートでもいいんじゃね?ということでテンプレートにしてみる。
#include <iostream> template<int W, int P> std::ostream& setformat(std::ostream& out) { if(P > 0) { out << std::fixed; out.precision(P); } if(W > 0) { out << std::right; out.width(W); } else { out << std::left; out.width(-W); } return out; } template<int W> std::ostream& setformat(std::ostream& out) { if(W > 0) { out << std::right; out.width(W); } else { out << std::left; out.width(-W); } return out; } int main(int, char* []) { using namespace std; const int n = 123; const double r = 123.45; const char s[] = "hoge"; cout << "n = [" << setformat< 10> << n << "]" << endl; cout << "n = [" << setformat<-10> << n << "]" << endl; cout << "r = [" << setformat< 10> << r << "]" << endl; cout << "r = [" << setformat<-10> << r << "]" << endl; cout << "r = [" << setformat< 10, 3> << r << "]" << endl; cout << "r = [" << setformat<-10, 3> << r << "]" << endl; cout << "s = [" << setformat< 10> << s << "]" << endl; cout << "s = [" << setformat<-10> << s << "]" << endl; return 0; }
補足。実際にマニピュレータを使う場面で、関数の呼び出しでなく関数ポインタをストリームに送るということをしています。どういう動きになっているのかはここで解説されています(endl
の下り)。こんなこと、これ読むまで知りませんでしたヨ。
条件分岐もなくせね?
パラメータが定数なら、どの条件で使われるかはコンパイル時にわかるので、条件分岐もなくせるはず。ということでなくしてみた。ちなみに、メンバ関数になっているのは、関数テンプレートでは部分特殊化ができないからというのがその理由。
#include <iostream> template<int W, bool WF, int P, bool PF> struct Format; template<int W, int P> struct Format<W, true, P, true> { static std::ostream& setformat(std::ostream& out) { out << std::fixed; out.precision(P); out << std::right; out.width(W); return out; } }; template<int W, int P> struct Format<W, false, P, true> { static std::ostream& setformat(std::ostream& out) { out << std::fixed; out.precision(P); out << std::left; out.width(-W); return out; } }; template<int W, int P> struct Format<W, true, P, false> { static std::ostream& setformat(std::ostream& out) { out << std::right; out.width(W); return out; } }; template<int W, int P> struct Format<W, false, P, false> { static std::ostream& setformat(std::ostream& out) { out << std::left; out.width(-W); return out; } }; template<int W, int P> std::ostream& setformat(std::ostream& out) { return Format<W, (W > 0), P, (P > 0)>::setformat(out); } template<int W> std::ostream& setformat(std::ostream& out) { return Format<W, (W > 0), 0, false>::setformat(out); } int main(int, char* []) { using namespace std; const int n = 123; const double r = 123.45; const char s[] = "hoge"; cout << "n = [" << setformat< 10> << n << "]" << endl; cout << "n = [" << setformat<-10> << n << "]" << endl; cout << "r = [" << setformat< 10> << r << "]" << endl; cout << "r = [" << setformat<-10> << r << "]" << endl; cout << "r = [" << setformat< 10, 3> << r << "]" << endl; cout << "r = [" << setformat<-10, 3> << r << "]" << endl; cout << "s = [" << setformat< 10> << s << "]" << endl; cout << "s = [" << setformat<-10> << s << "]" << endl; return 0; }