potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++11 STLの標準入出力にsetfill/setwをまとめて設定するヘルパー構造体&関数

はじめに

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)としないとコンパイルエラーとなるのでヘルパー関数として利用されています。

上記の定義でも同様の理由(関数にすればテンプレート引数の指定を省略できる)で目的の名前の関数を定義しています。