はじめに
C++とSTL(Standard Template Library)で標準(エラー)出力(std::cout、std::cerr)を使用して整数などを出力するとき、出力形式を指定するにはstd::hexやstd::setw/setfillといった関数を利用します。
このstd::setw/setfillは一度出力すると消えてしまうので、出力する度に... << std::setw << std::setfill(...) << ...
などと設定する必要があります。意味としては分かりやすいのですが、冗長で読みにくいコードとなってしまいます。
例えば0x1234という数値を16進数で出力する場、さらに8桁まで0で埋めて(0xNNNNNNNNの形式で)出力するには次のようにします。
#include <iostream> #include <iomanip> void main() { std::cout << "0x" << std::hex << std::setw(8) << std::setfill('0') << 0x1234 << std::endl; }
0x00001234
1回の出力(1234)でもそれなりに長くなりました。次は複数の数値を0xNNNNNNNNの形式で出力しようとして以下のようなコードを書いてみます。
#include <iostream> #include <iomanip> void main() { // パターンA std::cout << "パターンA" << std::endl; std::cout << "0x" << std::setw(8) << std::setfill('0') << std::hex << 0x1111 << std::endl; std::cout << "0x" << std::setw(8) << std::setfill('0') << std::hex << 0x2222 << std::endl; std::cout << "0x" << std::setw(8) << std::setfill('0') << std::hex << 0x3333 << std::endl; std::cout << "0x" << std::setw(8) << std::setfill('0') << std::hex << 0x4444 << std::endl; // パターンB std::cout << "パターンB" << std::endl; std::cout << "0x" << std::setw(8) << std::setfill('0') << std::hex << 0x1111 << std::endl << "0x" << 0x2222 << std::endl << "0x" << 0x3333 << std::endl << "0x" << 0x4444 << std::endl; }
出力結果
パターンA 0x00001111 0x00002222 0x00003333 0x00004444 パターンB 0x00001111 0x2222 0x3333 0x4444
setw等を全て指定したパターンAではどの出力も求める形式となっていますが、それらを2回目以降省略したパターンBではstd::hexのみ生き残っています。
std::setw/setfillをまとめる
上記ではstd::setw/setfillが出力の度に初期化されてしまうことを確かめました。ではまとめて設定するにはどうすれば良いのかと言えば、std::setw/setfillを真似て以下の様な構造体と関数を定義すれば解決します。
setfillw
#include <iostream> #include <iomanip> template <typename ELEMENT_TYPE> struct _setfillw { ELEMENT_TYPE c; std::streamsize w; _setfillw(ELEMENT_TYPE c, std::streamsize w) : w(w), c(c) {} }; template <typename ELEMENT_TYPE> inline _setfillw<ELEMENT_TYPE> setfillw(ELEMENT_TYPE c, std::streamsize w) { return _setfillw<ELEMENT_TYPE>(c, w); } template <typename ELEMENT_TYPE> inline std::ostream& operator << (std::ostream& os, const _setfillw<ELEMENT_TYPE>& manip) { return os << std::setw(manip.w) << std::setfill(manip.c); }
使い方は次のようになります。蛇足ですが、"0x"の前にsetfill/wあるいはsetfillwを指定すると"0x"が0で8桁に揃えられるので注意して下さい。
void main() { // パターンA std::cout << "パターンA" << std::endl; std::cout << std::hex << "0x" << setfillw('0', 8) << 0x1111 << std::endl << "0x" << setfillw('0', 8) << 0x2222 << std::endl << "0x" << setfillw('0', 8) << 0x3333 << std::endl << "0x" << setfillw('0', 8) << 0x4444 << std::endl; // パターンB std::cout << "パターンB" << std::endl; std::cout << std::hex << "0x" << setfillw('0', 8) << 0x1111 << std::endl << "0x" << 0x2222 << std::endl << "0x" << 0x3333 << std::endl << "0x" << 0x4444 << std::endl; std::cout << "<PRESS ANY KEY>" << std::endl; std::cin.get(); }
出力結果
パターンA 0x00001111 0x00002222 0x00003333 0x00004444 パターンB 0x00001111 0x2222 0x3333 0x4444 <PRESS ANY KEY>
備考
VS2013に付属のSTLでもsetfill/wはその名前のテンプレート関数と適当な名前のテンプレート構造体が定義され、別個にoperator <<
を定義することで実装されています。テンプレート関数は不要そうですが、現バージョンのC++の仕様上、template <typename A>f(A a)
はf(1)
で呼び出せますが、template <typename A> struct A {A() {...}};
はA<int>(1)
としないとコンパイルエラーとなるのでヘルパー関数として利用されています。
上記の定義でも同様の理由(関数にすればテンプレート引数の指定を省略できる)で目的の名前の関数を定義しています。