C/C++ では配列の要素数を知るために配列のバイト数を要素のバイト数で割るというイディオムがよく知られているのですが、演算子 sizeof
が返すのはあくまでも (要素の数ではなく) バイト数であることや、 C/C++ でのポインタ関連表記がややこしいことなどと併さって間違ったことをしてしまうというのはよくあり、そのような内容の投稿を見る機会がありました。
C++ > stdint.h > sizeof(int[][3])とsizeof(uint8_t[][3]) > 48と12
あらためて挙動を追ってみます。
sizeof
で一次元配列の大きさを取得する
まず簡単な一次元配列ならそれほど問題はありません。
#include <iostream>
static const int table[3] = {1, 0, 0};
int main() {
std::cout << "Table extent is " << std::endl;
std::cout << sizeof(table)/sizeof(*table) << std::endl;
return 0;
}
sizeof
で二次元配列の大きさを取得する
これが二次元配列だとどうなるでしょうか。
#include <iostream>
static const int table2[][3] = {
{ 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 1 },
{ 0, 1, 1 }
};
int main() {
std::cout << "Table extent is " << std::endl;
std::cout << sizeof(table2)/sizeof(*table2) << std::endl;
return 0;
}
このように表示されるのがわかると思います。
Table extent is 4
4
というのは table2
の第一次元の要素数ですね。 *table2
の型は int[3]
なので table2
全体のバイト数を *table2
のバイト数で割ると第一次元の要素数になるのです。
各次元の要素数を取得する
では配列の第二次元の要素数を取得するには sizeof
に渡すときに *
をひとつ多く付けることで可能です。
#include <iostream>
static const int table2[][3] = {
{ 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 1 },
{ 0, 1, 1 }
};
int main() {
std::cout << "Table extent of dimension 2 is " << std::endl;
std::cout << sizeof(*table2)/sizeof(**table2) << std::endl;
return 0;
}
そして、 C++11 の type_traits
には std::extent
というクラステンプレートがあり、各次元の要素数を取得することが出来ます。 型システムの恩恵を受けられるので、 sizeof
を使った古いやりかたはなるべく捨ててこちらに移行するべきでしょう。
以下のように使えます。
#include <iostream>
static const int table2[][3] = {
{ 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 1 },
{ 0, 1, 1 }
};
int main() {
std::cout
<< "Table extent is "
<< std::extent<decltype(table2),0>::value
<< "x"
<< std::extent<decltype(table2),1>::value
<< std::endl;
return 0;
}
Table extent is 4x3
配列全体の大きさ
多次元配列について「配列の大きさ」といったとき、その配列が__配列の配列__であると見たいのであれば上記の方法でいいのですが、__多次元の広さを持った配列__として解釈するのであれば table2
の大きさは 12
であって欲しいですよね。
そういう機能がないか調べてみたのですが、 C++11 にはそのような機能が見当らなかったので書いてみました。
#ifndef HEADER_0ea4160a60cf2063fa848012290e278f
#define HEADER_0ea4160a60cf2063fa848012290e278f
#include <cstddef>
#include <type_traits>
template<class T>
class fullextent {
private:
template<size_t r, size_t i>
static constexpr typename std::enable_if<(r!=1), size_t>::type helper(void) {
return std::extent<T, i>::value * helper<r-1, i+1>();
}
template<size_t r, size_t i>
static constexpr typename std::enable_if<(r==1), size_t>::type helper(void) {
return 1;
}
public:
typename std::enable_if<std::is_array<T>::value, size_t>::type
static constexpr value = std::extent<T, 0>::value * helper<std::rank<T>::value, 1>();
};
#endif
使い方は以下のような要領です。
#include <iostream>
#include <fullextent.h>
static const int table2[][3] = {
{ 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 1 },
{ 0, 1, 1 }
};
int main() {
std::cout
<< "Table extent is "
<< fullextent<decltype(table2)>::value
<< std::endl;
return 0;
}
まとめ
配列の大きさを知りたいときは sizeof
よりも std::extent
を使いましょう。