36
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

配列の大きさ

Last updated at Posted at 2017-01-15

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 にはそのような機能が見当らなかったので書いてみました。

fullextent.h
#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 を使いましょう。

36
33
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?