テンプレート
メタプログラミングは、コンパイル時にテンプレートを利用してソースコードを生成する
メタプログラミングの一種です。
コンパイラがテンプレートを解析し、具体的な型や値に基づいて新しいコードを生成します。この生成されたコードは、他のソースコードと結合されてコンパイルされます。テンプレートによって生成されるものには、コンパイル時の定数、データ構造、関数定義などがあります。
テンプレート
メタプログラミングは、コンパイル時にプログラムを実行するようなもので、
C++、
D言語、
Eiffel、
Haskell、ML、XLなどの様々な言語で利用されています。
テンプレートを利用した
メタプログラミングには、主に2つの段階があります。まず、テンプレートを定義し、次にそれをインスタンス化する必要があります。テンプレートは、生成するコードの一般化された形式を記述し、インスタンス化によって具体的なソースコードが生成されます。
テンプレート
メタプログラミングは
チューリング完全であり、理論的には通常のプログラムで実行できることは、テンプレートメタプログラムでも実行可能です。しかし、テンプレートはマクロとは異なります。マクロはコンパイル時に文字列操作によってコードを生成しますが、一般的に言語の意味や型を考慮することができません。一方で、テンプレートは型情報を保持し、より安全で高度なコード生成を可能にします。
また、テンプレートメタプログラムには変更可能な変数がありません。変数は初期化時に一度だけ代入され、その後は変更できません。この特性は関数型プログラミングに似ており、テンプレートの実装では
制御構造や再帰呼び出しが用いられることが多いです。
テンプレート
メタプログラミングの文法は、その言語の通常の文法とは大きく異なる場合があります。しかし、テンプレートを使用する主な理由は、ジェネリックプログラミングの実装と、コンパイル時のコード最適化です。ジェネリックプログラミングでは、似たようなコードを何度も書く必要がなくなり、コードの再利用性と保守性が向上します。また、テンプレートを使用すると、通常は最適化されない部分も最適化される可能性があり、プログラムのパフォーマンスが向上します。
コンパイル時のクラス生成
「コンパイル時のプログラミング」の一例として、
階乗計算の例を見てみましょう。以下はテンプレートを使用しない
C++のコードです。
cpp
int factorial(int n) {
if (n == 0) return 1;
return n
factorial(n - 1);
}
int main() {
int result1 = factorial(4);
int result2 = factorial(0);
return 0;
}
このコードは、実行時に4と0の階乗を計算します。一方、テンプレートメタプログラミングを使用すると、コンパイル時に階乗を計算できます。
cpp
template
struct Factorial {
static const int value = N Factorial
::value;
};
template <>
struct Factorial<0> {
static const int value = 1;
};
int main() {
int result1 = Factorial<4>::value;
int result2 = Factorial<0>::value;
return 0;
}
このコードでは、コンパイル時に4と0の階乗が計算され、結果は定数として扱われます。2つのバージョンは機能としては同じですが、前者は実行時に計算するのに対し、後者はコンパイル時に計算します。ただし、テンプレートパラメータの値はコンパイル時に決定している必要があり、Factorial::value はXがコンパイル時定数である場合にのみ使用できます。
コンパイル時のコード最適化
上記の階乗の例は、コンパイル時の最適化の一例です。プログラム内で必要な階乗の値を事前に計算し、定数としてコードに埋め込むことで、実行時の性能とメモリ使用量の両方を改善できます。しかし、より重要な最適化として、テンプレートメタプログラミングを使ったコンパイル時のループ展開があります。
例えば、n次元ベクトルクラスを考えてみましょう(nはコンパイル時に既知)。n次元ベクトルでは、ループ展開によって性能を大幅に向上させることができます。以下に加算演算子の例を示します。
cpp
template
class Vector {
T data[dimension];
public:
Vector operator+(const Vector& other) const {
Vector result;
for (int i = 0; i < dimension; ++i) {
result.data[i] = data[i] + other.data[i];
}
return result;
}
};
コンパイラがこのテンプレート関数をインスタンス化すると、次のようなコードが生成されます(dimension=3の場合)。
cpp
Vector operator+(const Vector& other) const {
Vector result;
result.data[0] = data[0] + other.data[0];
result.data[1] = data[1] + other.data[1];
result.data[2] = data[2] + other.data[2];
return result;
}
コンパイラは、テンプレートパラメータdimensionがコンパイル時定数であるため、forループをコンパイル時に展開することができます。この手法は、Boost C++ Libraryなどで実際に利用されています。
静的ポリモーフィズム
ポリモーフィズムは、派生クラスのオブジェクトを基底クラスのオブジェクトとして扱い、メソッドは派生クラスのものを使うことができる機能です。例えば、以下のようなコードがあります。
cpp
class Base {
public:
virtual void foo() { / ... / }
};
class Derived : public Base {
public:
void foo() override { / ... / }
};
void bar(Base& obj) {
obj.foo(); // 実行時にDerived::foo()が呼ばれる
}
virtualメソッドの呼び出しでは、仮想関数テーブルを参照してどのメソッドを呼び出すかが実行時に決定されます。これを「動的ポリモーフィズム」と呼び、実行時のオーバーヘッドが発生します。
しかし、多くの場合、ポリモーフィズムはコンパイル時に決定可能です。Curiously Recurring Template Pattern(CRTP)を使うことで、「静的ポリモーフィズム」を実現できます。これはポリモーフィズムに似た手法ですが、コンパイル時に解決されるため、実行時のオーバーヘッドを削減できます。
cpp
template
class Base {
public:
void foo() {
static_cast>(this)->foo_impl();
}
};
class Derived : public Base {
public:
void foo_impl() { / ... / }
};
void bar(Base& obj) {
obj.foo(); // コンパイル時にDerived::foo_impl()が呼ばれる
}
この例では、静的キャストを使って派生クラスのメンバを基底クラスのメンバ関数内で使用し、コンパイル時にポリモーフィズムのような機能を実現しています。CRTPは、Boostのイテレータライブラリなどで広く使用されています。同様の手法として、Barton-Nackmanトリックもあります。
テンプレートメタプログラミングには、以下のような利点と欠点があります。
コンパイル時と実行時のトレードオフ: テンプレートのコードはコンパイル時に処理されるため、コンパイル時間が長くなる可能性があります。しかし、実行コードはより最適化されます。
ジェネリックプログラミング: テンプレートメタプログラミングによって、プログラマはアーキテクチャに集中し、コンパイラに実装の詳細を任せることができます。これにより、真にジェネリックなコードが実現し、コードの縮小と保守性の向上につながります。
可読性: C++では、テンプレートメタプログラミングを使用したコードは複雑で読みにくい場合があります。特に、テンプレートメタプログラミングに慣れていないプログラマにとっては、コードの保守性が低下する可能性があります。
移植性: C++では、テンプレートメタプログラミングのサポートがコンパイラによって異なるため、移植性の問題が発生する可能性があります。
関連項目
メタプログラミング
プリプロセッサ
SFINAE
クワイン
参考文献
Ulrich W. Eisenecker: Generative Programming: Methods, Tools, and Applications
Andrei Alexandrescu: Modern C++ Design: Generic Programming and Design Patterns Applied
David Abrahams, Aleksey Gurtovoy: C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond
David Vandervoorde, Nicolai M. Josuttis: C++ Templates: The Complete Guide
Manuel Clavel: Reflection in Rewriting Logic: Metalogical Foundations and Metaprogramming Applications
What's Wrong with C++ Templates? by Jacob Matthews
外部リンク
The Boost Metaprogramming Library (Boost MPL)
The Spirit Library
The Boost Lambda library
"The" article by Todd Veldhuizen from 1995
Template Haskell, type-safe metaprogramming in Haskell
* Templates Revisited, D言語のテンプレートメタプログラミングに関する文