2014年1月5日日曜日

C++で数値を2進数表記で出力する方法 (コンパイル時編)

実行時に数値のビット列を出力する方法を前回紹介しました。
http://code-mynote.blogspot.jp/2014/01/c2.html
ここでは、コンパイル時にビット列の文字列定数を用意したい場合(他のメタ関数に渡したり、実行時の文字列生成コストが無視できない場合など)を考えます。
自分の経験では今までそんな場合は無かったのですが、boost::mplの勉強を兼ねて下記のメタ関数を作ってみました。

#include <boost/mpl/string.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/bitwise.hpp>

namespace mpl = boost::mpl;

template <typename c>
struct bin_string {
    typedef typename boost::mpl::push_back<
        typename bin_string<typename mpl::shift_right<c, mpl::int_<1> >::type>::type,
        boost::mpl::char_<'0' + (c::value & 1)>
        >::type type;
};
template <typename T>
struct bin_string<mpl::integral_c<T, 0> > : boost::mpl::string<> {};

template <long long unsigned N>
struct bin_string_c : bin_string<mpl::integral_c<long long unsigned, N> > {};

処理内容

bin_stringがメタ関数の本体、bin_string_cが実行時に呼び出すメタ関数の転送役です。
templateを使用しているので見た目が少々違いますが、やってることは前回実装したto_bin_strと同じです。
数値を1つずつ右にシフトし、右端のビットを0か1の文字に変換することを数値が0になるまで(1のビットが無くなるまで)繰り返します。
再帰で実装しているので左端のビットが出力文字列でも左端となり、最後に文字列を反転する必要がありません。

使用例

メタ関数の返す文字列はmpl::c_str<>に渡すことでコンパイル時にconst char[]文字列が生成されます。
長いので、最初だけフルの名称で呼び出し、後はマクロ定義の呼び出しをしています。
下段のformat_bin_stringは表示桁を揃えたい場合に使用するメタ関数です。(ソースは後述)

#include <iostream>

namespace mpl = boost::mpl;

#define BINSTR(x) mpl::c_str<bin_string_c<x>::type >::value
#define FBINSTR(x, n) mpl::c_str<format_bin_string<bin_string_c<x>::type, n>::type>::value

int main() {
    const char x_c = 4;
    const unsigned y_c = 11;
    const int z_c = 103;
    const unsigned long long w_c = 4527856554780ULL;
    std::cout << mpl::c_str<bin_string_c<x_c>::type >::value << std::endl;
    std::cout << BINSTR(y_c) << std::endl;
    std::cout << BINSTR(z_c) << std::endl;
    std::cout << BINSTR(w_c) << std::endl;

    std::cout << mpl::c_str<format_bin_string<bin_string_c<x_c>::type, n>::type>::value << std::endl;
    std::cout << FBINSTR(y_c, n) << std::endl;
    std::cout << FBINSTR(z_c, n) << std::endl;
    std::cout << FBINSTR(w_c, n) << std::endl;
}

出力結果

100
1011
1100111
1000001111000111001010010000001001100011100
00100
01011
00111
11100

メタ関数format_bin_stringのソース。


template <typename Sequence1, typename Sequence2>
struct concat :
    mpl::insert_range<Sequence1, typename mpl::end<Sequence1>::type, Sequence2> {};

template <int N>
struct zero_string :
    mpl::push_back<typename zero_string<N-1>::type, mpl::char_<'0'> > {};
template <>
struct zero_string<0> : mpl::string<> {};

template <typename Sequence, int DigitNum, bool greater = (DigitNum >= mpl::size<Sequence>::value)>
struct format_bin_string {};

template <typename Sequence, int DigitNum>
struct format_bin_string<Sequence, DigitNum, true> :
    concat<
        typename zero_string<DigitNum - mpl::size<Sequence>::value>::type,
        Sequence
    > {};

template <typename Sequence, int DigitNum>
struct format_bin_string<Sequence, DigitNum, false> :
    mpllib::tail_n_c<Sequence, DigitNum> {};

mpllib::tail_n_cのソース。

#include <boost/mpl/size.hpp>
#include <boost/mpl/advance.hpp>
#include <boost/mpl/erase.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/less.hpp>
#include <boost/mpl/minus.hpp>

namespace mpllib {
namespace mpl = boost::mpl;

template <typename Sequence, typename N>
struct erase_head_n {
    typedef typename mpl::begin<Sequence>::type begin_iter;
    typedef typename mpl::advance<begin_iter, N>::type cut_iter;
    typedef typename mpl::erase<Sequence, begin_iter, cut_iter>::type type;
};

template <typename Sequence, typename N>
struct tail_n {
    typedef typename mpl::eval_if<
        mpl::less<mpl::size<Sequence>, N>,
        Sequence,
        erase_head_n<Sequence, mpl::minus<mpl::size<Sequence>, N> >
    >::type type;

};

template <typename Sequence, int N>
struct tail_n_c {
    typedef typename tail_n<Sequence, mpl::int_<N> >::type type;
};

}; // end of namespace mpllib

0 件のコメント:

コメントを投稿