Loading AI tools
ウィキペディアから
テンプレートメタプログラミング(英: template metaprogramming)は、メタプログラミング技法の一種であり、コンパイラがテンプレートを使って一時的ソースコードを生成し、それを他のソースコードと結合してコンパイルする方式である。テンプレートが出力するものは、コンパイル時の定数、データ構造、関数定義などがある。テンプレートの利用は言わばコンパイル時の実行である。この技法は様々な言語で使われている(C++、D言語、Eiffel、Haskell、ML、XLなど)。
メタプログラミング手法としてのテンプレート利用には2段階の操作が必要である。まずテンプレートを定義し、次にそれをインスタンス化しなければならない。テンプレートは生成すべきコードの一般化された形式を示し、インスタンス化によってそのテンプレートから具体的なソースコードが生成される。
テンプレートメタプログラミングは一般にチューリング完全であり、コンピュータプログラムで実行できることはテンプレートメタプログラムでも実行できる。
テンプレートはマクロとは異なる。マクロもコンパイル時に使われる機能で、文字列操作によってソースコードを生成する。一般的にソースコード中の字を置き換える形式のマクロ機能は、言語の意味とか型といったものを考慮できない(LISPのマクロはこの限りではない)。
テンプレートメタプログラムには変更可能な変数がない。つまり、変数は初期化時に一回代入を行うだけである。これは一種の関数型プログラミングと言える。実際、テンプレートの実装では制御構造や再帰呼び出しだけを実装していることが多い。
テンプレートメタプログラミングの文法はそのプログラミング言語本来のそれとだいぶ異なることが多いが、それでもテンプレートを使うのには理由がある。理由のひとつとしてジェネリックプログラミングの実装をするためということが挙げられる(似たようなコードをいくつも書かないようにする)。また、コンパイル時のコードの最適化の利点を最大限生かすためという理由もある。通常では最適化されない部分もテンプレートを使っていると最適化される場合がある。
「コンパイル時のプログラミング」とはどういう意味かを示すため、階乗関数の例を示す。以下はテンプレートを使わない場合のC++のコードである:
int factorial(int n)
{
if (n == 0)
return 1;
return n * factorial(n - 1);
}
void foo()
{
int x = factorial(4); // == (4 * 3 * 2 * 1) == 24
int y = factorial(0); // == 0! == 1
}
このコードは 4 と 0 の階乗を求めている。
テンプレートメタプログラミングとテンプレートの特殊化を再帰のために使って階乗の計算を実装すると、実行時に階乗の計算をせずにコンパイル時に計算を行うことができる:
template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0>
{
enum { value = 1 };
};
// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
int x = Factorial<4>::value; // == 24
int y = Factorial<0>::value; // == 1
}
このコードは 4 と 0 の階乗をコンパイル時に計算し、その結果を定数のように扱う。
これら2種類のバージョンはどちらもプログラムの機能という意味では同じであるが、前者は階乗をプログラム実行時に計算しているのに対して、後者はそれをコンパイル時に計算する。しかし、このようにテンプレートを使うには、テンプレートのパラメータの値がコンパイル時に判っていなければならず、Factorial<
X>::value
は X がコンパイル時に決まっている場合にしか使えない。換言すれば、X はコンパイル時定数(リテラルや sizeof
など)の呼び出し結果でなければならない。
上記の階乗の例はコンパイル時の最適化の一例であり、プログラム内で必要とされる階乗の値を予め計算して、定数としてコード内に組み込んでコンパイルを行う。これは実行時の性能とメモリ使用量の両面で効果がある。しかし、これはそれほど大きな最適化ではない。
もっと重要なコンパイル時の最適化として、テンプレートメタプログラミングを使ったコンパイル時のループ展開がある。ここでは n 次元のベクトルクラスの作成を例として示す(n はコンパイル時に分かっている)。n 次元ベクトルではループ展開によって性能が大きく改善される。ここでは加算演算子を例として示す。
template<int dimension>
Vector<dimension>& Vector<dimension>::operator+=(const Vector<dimension>& rhs)
{
for (int i = 0; i < dimension; ++i)
value[i] += rhs.value[i];
return *this;
}
コンパイラがこのテンプレート関数をインスタンス化すると、次のようなコードが生成される:
template<>
Vector<2>& Vector<2>::operator+=(const Vector<2>& rhs)
{
value[0] += rhs.value[0];
value[1] += rhs.value[1];
return *this;
}
コンパイラは、テンプレートのパラメータ dimension
がコンパイル時に定数なので、for
ループをコンパイル時に展開できる。この手法の具体的実装例がBoost C++ Library等にある[1]。
ポリモーフィズムを使えば、派生クラスのオブジェクトを元となるクラスのオブジェクトのように扱いつつ、メソッドは派生クラスのものを使うことができる。例えば、次のようなコードである:
class Base
{
public:
virtual void method() { std::cout << "Base"; }
};
class Derived : public Base
{
public:
virtual void method() { std::cout << "Derived"; }
};
int main()
{
Base *pBase = new Derived;
pBase->method(); //outputs "Derived"
return 0;
}
virtual
メソッドの呼び出しでは最も下位のクラスのメソッドが使われる。「動的ポリモーフィズム」では、virtualメソッドを持つクラスについて仮想関数テーブル (virtual look-up table) が生成され、実行時にそのテーブルを参照してどのメソッドを呼び出すかが決められる。従って、「動的ポリモーフィズム」では実行時のオーバヘッドが避けられない。
しかし、多くの場合ポリモーフィズムは動的である必要がなく、コンパイル時に決定可能である。そこで、Curiously Recurring Template パターン (CRTP) を使って「静的ポリモーフィズム」を実現できる。これはコード上はポリモーフィズムに似せた手法であってポリモーフィズムそのものではないが、コンパイル時に解決されるため、オーバヘッドを削減できる。以下に例を示す:
template <class Derived>
struct base
{
void interface() {
// ...
static_cast<Derived*>(this)->implementation();
// ...
}
};
struct derived : base<derived>
{
void implementation();
};
ここで、メンバ関数本体が宣言のずっと後にインスタンス化されるという事実を利用して、static_cast
を使って派生クラスのメンバをベースクラスのメンバ関数内で使い、コンパイル時にポリモーフィズム的な機能を実現している。CRTPは実際にBoostのイテレータライブラリで広く使われている。
類似の使用法として "Barton-Nackman trick" がある。
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.