型のビット数には大きく分けて2種類があります。ひとつは記憶域全体のビット数で、もうひとつは記憶域のうち符号ビットも含めて値を表現するためのビット数です。なお、ここでいう型とはスカラ型(整数型・浮動小数点型・ポインタ型)を指すものとします。
前者、すなわち記憶域全体のビット数を求めるのであれば、元ネタのマクロを使えばいいでしょう。今回は後者、すなわち値を表現するビット数(有効ビット数と呼ぶことにします)を求めることにします。
記憶域全体のビット数と有効ビット数が異なる具体例としては、long double型の内部表現が拡張倍精度浮動小数点数の処理系であれば有効ビット数は80ビットですが、境界調整の関係で記憶域全体は96ビットになっている処理系があります。あるいは、メモリ空間が16Mバイトのマイコンの場合、ポインタの有効ビット数は24ビットですが、やはり境界調整の関係で記憶機全体は32ビットになっている処理系があります。
前置きはこれぐらいにして、整数型・浮動小数点型・ポインタ型それぞれについて、有効ビット数を求めるためのヘルパーメタ関数を作っていきます。
まずは整数型からです。
0 1 2 3 4 5 6 7 |
template <typename T, bool Integer> struct bit_size_of_helper { typedef std::numeric_limits<T> l; static const std::size_t value = l::is_signed + l::digits; }; |
次は浮動小数点型です。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
template <std::size_t E> struct bit_size_from_range { static const std::size_t value = (E != 0) + bit_size_from_range<E >> 1>::value; }; template<> struct bit_size_from_range<0> { static const std::size_t value = 0; }; template <typename T> struct bit_size_of_helper<T, false> { typedef std::numeric_limits<T> l; static const std::size_t value = 1 + bit_size_from_range<l::max_exponent - l::min_exponent>::value + l::digits; }; |
かなり複雑になってしまいましたが、指数部のビット数を直接知る方法がないため、指数部の表現範囲(max_exponent – l::min_exponent)から間接的に導いているためです。なお、上記のテンプレートでは、実際のオブジェクトのビットパターンには含まれないビットも数えてしまっています。具体的には、IEC 60559の単精度または倍精度浮動小数点フォーマットでは、仮数部最上位の1は、ビットパターンには含まれませんが暗黙的に存在します。そのため、IEC 60559の単精度に準拠したfloat型のビット数を調べると33ビットになってしまいます。これを補正することは可能ですが、とりあえずこのままにしておきます。
最後はポインタ型ですが、これは処理系に応じて手作業で部分特殊化を行う以外にありません。なぜなら、ポインタのサイズとしては32ビットでも、実際のメモリ空間が20ビットや24ビットの処理系では、残りのビットが詰め物ビットになるからで、これを判別するための一般的な方法は存在しません。
また、処理系によっては型によってポインタのサイズが異なる場合があるので要注意です。さすがに、char型とint型でポインタのサイズが異なる処理系は最近は見かけませんが、オブジェクト型と関数型でポインタのサイズが異なる処理系は普通に見かけます。また、const修飾子の有無でポインタのサイズが異なる場合もあります(ROMはfar領域、RAMはnear領域に配置されるなどの理由)。
const修飾子の有無によって部分特殊化を行うのは簡単ですので、ここでは割愛させていただいて、オブジェクト型のポインタと関数型のポインタを一発で見分ける方法だけを考えることにします。
0 1 2 3 4 5 6 |
typedef char yes_type; typedef struct { char a[2]; } no_type; yes_type test_pointer_of_object(const volatile void*); no_type test_pointer_of_object(...); |
多重定義されたtest_pointer_of_objectにポインタを渡した場合、関数へのポインタ型は、暗黙的にvoid*に型変換されることはありませんので、後者が選択されることになります。test_pointer_of_objectは返却値のサイズが確実に異なりますので、サイズを調べてやれば、そのポインタがオブジェクトへのポインタか、関数へのポインタかを知ることができます。
0 1 2 3 4 5 6 7 |
template <typename T> struct bit_size_of_helper<T*, false> { static const std::size_t value = sizeof test_pointer_of_object((T*)0)==sizeof(yes_type)?16:32; }; |
上の例では、オブジェクトへのポインタ型であれば16ビット、関数へのポインタ型であれば32ビットに定義しています。この部分は処理系に応じて手作業で修正する必要があります。
なお、今回はC++98以降で使えるように自分でyes_typeとno_typeを用意しましたが、C++11以降であれば、std::true_typeとstd::false_typeを使ってstd::is_sameで比較するようにしてもよいでしょう。
ここまではあくまでもヘルパーメタ関数ですので、最後にこれをまとめて、実際にユーザーが利用できる形にする必要があります。
0 1 2 3 4 5 6 7 8 |
template <typename T> struct bit_size_of { typedef std::numeric_limits<T> l; static const std::size_t value = bit_size_of_helper<T, l::is_integer>::value; }; |
ちょっと長くなってしまいましたが、これで一通り完成です。今回取り上げたテンプレートは、元ネタとは異なり、論理的なビット数を取得することができます。型のサイズが2の累乗バイトになっていない場合でも、詰め物ビットがある場合でも、正しくビット数を求めることができます。