2014年1月4日土曜日

C++で64bit整数に対応したビットやビットマスク定数の生成

C++でビットの定数を書く時に、いちいち4096とか、(1 << 12)のように書くのは面倒です。
特に64bit整数の大きさのビットの場合、2305843009213693952と書かれても意味不明ですし、(1ULL << 62)も少々見づらいです。
さらにビットマスクを書く場合、2305843009213693951や((1ULL << 62) - 1)などとても見づらくなります
そこで今回、bit<N>::value、bitmask<N>::valueでアクセスできるビット定数を作ります。

コンパイル時ビット

まず、bit<N>::valueとして(1 << N)が取得できるメタ関数bitを用意します。
桁数が大きくて64bit整数が必要になった時も型を自動選択できるように、uint_selectorというメタ関数も用意しました。

#include <boost/mpl/if.hpp>
namespace mpl = boost::mpl;

const int BITS_PER_BYTE = 8;

template <int DigitNum>
struct uint_selector :
    mpl::if_c<
        (DigitNum > sizeof(unsigned) * BITS_PER_BYTE),
        unsigned long long,
        unsigned
    > {};

template <int DigitNum, typename T = typename uint_selector<DigitNum>::type>
struct bit {
    typedef T type;
    static const T value = (static_cast<T>(1) << (DigitNum - 1));
};
template <>
struct bit<0> {
    enum {value = 0};
};

型は、明示的にも指定できるようにしてあります。

実行サンプル

typedef unsigned long long ull_t;

int main() {
    const auto bit1 = bit<3>::value;
    const ull_t bit2 = bit<5, ull_t>::value;
    const auto bit3 = bit<64>::value;
    std::cout << bit1 << std::endl;
    std::cout << bin_string<bit1>::str() << std::endl;
    std::cout << bit2 << std::endl;
    std::cout << bin_string<bit2>::str() << std::endl;
    std::cout << bit3 << std::endl;
    std::cout << bin_string<bit3>::str() << std::endl;
}

なお、bin_string::str()は2進数表記のstd::stringを生成する自作の関数です。

実行結果

4
100
16
10000
9223372036854775808
1000000000000000000000000000000000000000000000000000000000000000

コンパイル時ビットマスク

次にこれを利用し、bitmask<N>::valueとしてビットマスクを取得できるメタ関数bitmaskを用意します。

template <int DigitNum, typename T = typename uint_selector<DigitNum>::type>
struct bitmask {
    typedef T type;
    static const T value = bit<DigitNum + 1>::value - 1;
};

bitと同様に、型は明示的にも指定できるようにしてあります。

実行サンプル

typedef unsigned long long ull_t;

int main() {
    const auto mask1 = bitmask<3>::value;
    const ull_t mask2 = bitmask<5, ull_t>::value;
    const auto mask3 = bitmask<64>::value;
    std::cout << mask1 << std::endl;
    std::cout << bin_string<mask1>::str() << std::endl;
    std::cout << mask2 << std::endl;
    std::cout << bin_string<mask2>::str() << std::endl;
    std::cout << mask3 << std::endl;
    std::cout << bin_string<mask3>::str() << std::endl;
}

実行結果

7
111
31
11111
18446744073709551615
1111111111111111111111111111111111111111111111111111111111111111

いずれもコンパイル時に定数を生成するメタ関数なので、通常の定数と同じく実行時のコストはゼロで使用することができます。

0 件のコメント:

コメントを投稿