☆C++11テクニック☆ 配列を配列で初期化する方法+α

メンバー変数の初期化

 例えば、こんなコードがあったとします。

template <class T>
struct Hoge
{
  Hoge(const T &v) : value(v) {}

  T value;
};

template <class T>
Hoge<T> make_hoge(const T &v)
{
  return Hoge<T>(v);
}

#include <iostream>
#include <cxxabi.h>
#include <cstdlib>
#include <memory>

template <class T>
std::ostream &print(std::ostream &os, const T &value)
{
  int status;
  std::unique_ptr<char, decltype(&std::free)> demangled(abi::__cxa_demangle(typeid(value).name(), 0, 0, &status), &std::free);
  return os << demangled.get() << ": " << value << std::endl;
}

int main()
{
  auto hoge = make_hoge(142857);

  print(std::cout, hoge.value);

  return 0;
}

 結果はもちろん、こうなります。

int: 142857

メンバー変数が配列になると……?

 では次に、make_hoge()に渡す実引数を次のように変えてみましょう。

  constexpr auto hoge = make_hoge("142857");

 コンパイルすると、次のようなエラーが発生します。

main.cpp: In instantiation of 'Hoge<T>::Hoge(const T&) [with T = char [7]]':
main.cpp:12:19:   required from 'Hoge<T> make_hoge(const T&) [with T = char [7]]'
main.cpp:30:29:   required from here
main.cpp:4:29: error: array used as initializer
   Hoge(const T &v) : value(v) {}

 どうやら、「配列をvalue(v)の形式で初期化できないよ」と言われているようです。  仕方ないので、配列の時だけコンストラクタを特殊化して、次のようにしてみましょう:

template <class T, int N>
struct Hoge<T[N]>
{
  Hoge(const T (&v)[N]) { std::copy_n(&v[0], N, &value[0]); }

  T value[N];
};

 出力は、こうです。

char [7]: 142857

 おお、上手く行きました!  では次は、このクラスをコンパイル時に構築できるよう、Hogeのコンストラクタやmake_hoge()constexprをつけてみましょう。  コンパイル結果は、こうなります:

main.cpp: In constructor 'constexpr Hoge<T [N]>::Hoge(const T (&)[N])':
main.cpp:14:71: error: constexpr constructor does not have empty body
   constexpr Hoge(const T (&v)[N]) { std::copy_n(&v[0], N, &value[0]); }
                                                                       ^
main.cpp: In function 'int main()':
main.cpp:40:39: error: the type 'const Hoge<char [7]>' of constexpr variable 'hoge' is not literal
   constexpr auto hoge = make_hoge("142857");
                                       ^
main.cpp:12:8: note: 'Hoge<char [7]>' is not literal because:
 struct Hoge<T[N]>
        ^
main.cpp:12:8: note:   'Hoge<char [7]>' is not an aggregate, does not have a trivial default constructor, and has no constexpr constructor that is not a copy or move constructor

 どうやら、constexprなコンストラクタに中身がある事がお気に召さない様子。C++14では(std::copy_n()constexprでさえあれば)大丈夫なのかもしれませんが、少なくともgcc4.9.0では-std=c++1yをつけてやってもダメです。  では、どうすればよいのでしょうか?

 さて、ここでC++の配列初期化方法を思い出してみます。

int hoge[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

 つまり、この方法を利用すれば、コンストラクタの中身を空にする事ができるのでは?  ……はい、その通りです。

template <class T, int N>
struct Hoge<T[N]>
{
  constexpr Hoge(const T (&v)[N]) : value{/* 0, 1, 2, ... */} {}

  T value[N];
};

 それではこのvalue{}の中身は、どう書けばよいのでしょうか……というのが今回の本題になります。ああ長かった。

N要素の配列メンバー変数の初期化

 配列の要素数ごとにvalueの初期化子の要素数が変わるので、任意のNに対して適切な初期化子を書くのは非常に困難なように見えます。  C++メタプログラミングを始めたばかりの人であれば、最初に思いつくのはこんなコードでしょう:

template <class T>
struct Hoge<T[1]>
{
  constexpr Hoge(const T (&v)[1]) : value{v[0]} {}

  T value[1];
};

template <class T>
struct Hoge<T[2]>
{
  constexpr Hoge(const T (&v)[2]) : value{v[0], v[1]} {}

  T value[2];
};

template <class T>
struct Hoge<T[3]>
{
  constexpr Hoge(const T (&v)[3]) : value{v[0], v[1], v[2]} {}

  T value[3];
};

 利用する全てのNについて、コンストラクタを特殊化してしまえばよろしい。解決ですな。

 ……もちろん、そんな頭の悪い事など推奨できるはずもありません。だってこれ、#defineすらできないじゃん。できてもやりませんが。  C++11erであれば、当然次のように書くでしょう:

template <class T, int N>
struct Hoge<T[N]>
{
  template <int ... i>
  constexpr Hoge(const T (&v)[N]) : value{v[i] ...} {}

  T value[N];
};

 さて問題は、このint ... iをどのように生成するか、になります。  もちろん、Hoge<T[N]><0, 1, 2>("10")なんて事を手動で書きたくはありません。iの数を間違えると、余裕で配列外にアクセスしてしまいますし。

Nから0...N-1を作る方法

 こんな時、Nから0...N-1のリストを作れるようにすれば、物事は万事解決です。  ちょっとだけクラス数は増えますが、こんな感じになればよいでしょうか?

template <int N>
class make_indices
{
public:
  template <int ... i> struct indices {};
  typedef indices<i ...> type; // ←ココ
};

template <class T, int N>
class Hoge_impl
{
  friend class Hoge<T[N]>; // コンストラクタのみHoge<T[N]>に公開する

private:
  template <int ... i>
  constexpr Hoge_impl(const T (&v)[N], indices<i...>) : value{v[i] ...} {}

public:
  T value[N];
};

template <class T, int N>
struct Hoge<T[N]> : public Hoge_impl<T, N>
{
  constexpr Hoge(const T (&v)[N]) : Hoge_impl<T, N>(v, typename make_indices<N>::type()) {}
};

 この、make_indices<N>::typeを生成する方法さえ見つかれば、全ては解決です。

 アイディアとしては、テンプレートの再帰を使います。例えばこんな感じ:

template <int N>
class make_indices
{
private:
  // 最初はM==Nで、Mを1つ減らすごとにsizeof...(i)を1増やす
  template <int M, int ... i>
  struct make_indices_impl : public make_indices_impl<M - 1, i ... , sizeof...(i)> {} ;

  // M==0となったので、i...には「0〜N-1」のN連続の値が入っている
  // この時のindices<i...>が、求めるindices<i...>
  template <int ... i>
  struct make_indices_impl<0, i...>
  {
    typedef indices<i ...> type ;
  } ;

public:
  typedef typename make_indices_impl<N>::type type;
};

 実行してみると、

char [7]: 142857

 この通り、予期した通りに動作する事がわかります。

オマケ:今のの応用

 これを応用すると、配列をコンパイル時に逆転させてみたり、

template <class T, int N>
class Hoge_impl
{
  template <int M, int ... i1, int ... i2>
  constexpr Hoge_impl(const T (&v1)[M], indices<i1...>, const T (&v2)[N-M], indices<i2...>) : value{v1[i1] ..., v2[i2] ...} {}

public:
  T value[N];
};

のようにすると配列をコンパイル時に結合してみたりと、テンプレートメタプログラミングの幅が広がります。  もっとも、そこまで高度なメタプログラミングをしなければならないのなら、わざわざ車輪を再発明しなくても素直にSproutライブラリなりCELなりを使った方がずっと良いと思いますが。