【C++】静的配列をコンパイル時に動的生成する方法


0からNまでの固定長配列/静的配列{0, 1, 2, ...N}をコンパイル時に動的に生成する方法です。各要素を独自に計算することも可能です({1, 2, 4, 8}等)。

constexprコンストラクタを用いる方法とテンプレートメタプログラミングを用いる方法があります。

constexprコンストラクタ

単純な配列であれば、こちらのほうが簡単です。

template<int N> struct Seq {
  int ary[N];
  constexpr Seq() : ary() {
    for (int i = 0; i < N; i++) ary[i] = i;
  }
};

C++14規格のコンパイラが必要です。

constexpr auto seq = Seq<9>();
for (int v : seq.ary)
  printf("%d,", v); // 0,1,2,3,4,5,6,7,8,

汎用化

このようなクラスを作れば、配列要素をユーザ独自に拡張することも可能になります。

using Sequencer = int(*)(int);

template<int N, constexpr Sequencer FN> struct Sequence {
  int ary[N];
  constexpr Sequence() : ary() {
     for (int i = 0; i < N; i++) ary[i] = FN(i);
  }
};

Mul, Powのような関数を独自に定義&指定して使います。

constexpr int Mul(int n) { return 2 * n; }
constexpr int Pow(int n) { return 1 << n; }

int main() {
  // 偶数
  constexpr auto a = Sequence<9, Mul>();
  for (int v : a.ary)
    printf("%d,", v); // 0,2,4,6,8,10,12,14,16,
  // 累乗
  constexpr auto b = Sequence<9, Pow>();
  for (int v : b.ary)
    printf("%d,", v); // 1,2,4,8,16,32,64,128,256,
}

テンプレートメタプログラミング

再帰テンプレートを用いて実現します。

template<int E, int... A> struct Seq { static constexpr auto value = Seq<E - 1, E, A...>::value; };
template<int... A> struct Seq<0, A...> { static constexpr int value[] = {0, A...}; };
template<int... A> constexpr int Seq<0, A...>::value[];

こちらはC++11規格のコンパイラでもコンパイル可能です。

for (int i = 0; i < 9; i++)
  printf("%d,", Seq<9>::value[i]); // 0,1,2,3,4,5,6,7,8,

TMPを用いたこちらの方法であれば、生成される配列のサイズを可変的に調整することも可能になります。例えば123という数字を{1, 2, 3}形式の配列に変換するようなケースです(1234が指定された場合は要素数4の配列{1, 2, 3, 4}を動的に生成しなければならない)。

これが意外と簡単に思えて実は実現が難しいケースなのですが、TMPを用いればこれが容易に実現出来ます。

実際に似たようなことを以前FizzBuzz問題で実装したことがあるので、実現方法が気になる方は「コンパイル時FizzBuzz」を参考にしてみるのも良いかと思います。

逆に同じことを「constexprコンストラクタ」の方法で実現する場合、現状のC++では汎用化なコードを書くことが難しく、コードも煩雑になる傾向にあると感じます。

広告