_countof は、固定長配列の要素数を取得するためのマクロです。
古く C 言語時代から使用されている要素数を調べるためのマクロは、
#define ELEM_NUM(a) (sizeof(a) / sizeof((a)[0]))
こんな風になっているのですが、これだとポインタの要素数も取れてしまいます。
int ar[5];
int* par = ar;
int size = ELEM_NUM(par);
型安全で静的に要素数を取得するには _countof マクロを使用します。
int ar[5];
int* par = ar;
_countof(par);
今日はこの _countof マクロの実装について調べてみようと思います。
_countof の定義を見てみると、こんな風になっています。
template <typename _CountofType, size_t _SizeOfArray>
char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
#define _countof(_Array) sizeof(*__countof_helper(_Array))
……これをパッと見て理解できたそこのあなた。あなたは間違いなく変態です。
まあそれはおいといて、とりあえず分かりづらいのでいらない値を取り除いて整形しておきます。
template <typename Type, size_t Size>
char (*__countof_helper(Type (&ar)[Size]))[Size];
#define _countof(ar) sizeof(*__countof_helper(ar))
で、まずはこのマクロから離れて、ウォーミングアップとして固定長配列に慣れるとこから始めてみます。
まず小手調べ。
void func(Hoge (&ar)[10]);
こうやって書くと、ar は固定長配列への参照(Hoge[10]& 型)になります。
Hoge[10]& 型以外は受け付けません。例えば、
Hoge ar[10];
func(ar);
Hoge* par = ar;
func(par);
Hoge ar2[5];
func(ar2);
こんな風にポインタや要素数の違う配列を渡すと、型の不整合でコンパイル時にエラーになってくれます。
次に、固定長配列へのポインタを返す関数の宣言を考えてみます。
typedef Hoge (*HogeArrayPtr)[10];
HogeArrayPtr func();
こうなります。
Hoge[10]* 型を返す関数 func の出来上がりです。
これを、typedef を使わずに書いてみるとどうなるか分かりますか?
Hoge (*func())[10];
こうなります。何か難しい感じがしますが、これは↑の typedef を使った構文と全く同じ意味です。
で、これを踏まえた上で __countof_helper を読んでみると、先ほどの固定長配列を返す関数の構文と同じであることに気が付きます。
char (*__countof_helper(Type (&ar)[Size]))[Size];
Type (&ar)[Size] というのは、Type[Size] への参照を取る ar の宣言を意味していて、char (*func(...))[Size] というのは、char[Size] へのポインタを取る func() の宣言を意味しています。
つまり、__countof_helper というのは Type[Size]& 型の引数を取って char[Size]* 型を返す関数(の宣言)ということです。
これを typedef してみると、
typedef char (*CharArrayPtr)[Size];
typedef Type (&TypeArrayRef)[Size];
CharArrayPtr __countof_helper(TypeArrayRef ar);
こんな感じになります(本当は Size は template 引数なのでこうするのは不可能ですが)。
ここまでくれば後は簡単で、__countof_helper は char[Size]* 型を返すので、
*__countof_helper(ar);
とすれば char[Size] 型が返され、
sizeof(*__countof_helper(ar));
とすれば char[Size] の大きさ、つまり sizeof(char) * Size の大きさがコンパイル時に求められます。
これが _countof の定義です。
自分が _countof の定義を見て学んだ一番のことは、「関数ポインタと固定長配列を typedef せずに関数の戻り値として使うのはやめておけ」ということでした。
みなさんもこのエントリで何か見つけて頂ければと思います。