はじめに
C++のテンプレートシステムを使って、コンパイル時に計算を行う技法を解説する。
「テンプレート黒魔術」とも呼ばれるけど、基本を押さえればそこまで難しくない。
TMPとは
テンプレートメタプログラミングは、テンプレートの特殊化と再帰を使ってコンパイル時に計算を行う技法です。
1. 階乗(基本)
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
// 使用例
constexpr int fact5 = Factorial<5>::value; // 120
2. フィボナッチ数列
template<int N>
struct Fibonacci {
static constexpr int value =
Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template<>
struct Fibonacci<0> {
static constexpr int value = 0;
};
template<>
struct Fibonacci<1> {
static constexpr int value = 1;
};
// Fib(10) = 55
3. 型リスト
template<typename... Types>
struct TypeList {
static constexpr size_t size = sizeof...(Types);
};
// 型リストの先頭を取得
template<typename List>
struct Front;
template<typename T, typename... Rest>
struct Front<TypeList<T, Rest...>> {
using type = T;
};
// 使用例
using MyList = TypeList<int, double, char>;
using FirstType = Front<MyList>::type; // int
4. 条件分岐
template<bool Condition, typename Then, typename Else>
struct If;
template<typename Then, typename Else>
struct If<true, Then, Else> {
using type = Then;
};
template<typename Then, typename Else>
struct If<false, Then, Else> {
using type = Else;
};
template<bool Condition, typename Then, typename Else>
using If_t = typename If<Condition, Then, Else>::type;
// sizeof(int) == 4 なら int、そうでなければ long
using Result = If_t<(sizeof(int) == 4), int, long>;
5. 素数判定
template<int N, int D>
struct IsPrimeHelper {
static constexpr bool value =
(N % D != 0) && IsPrimeHelper<N, D - 1>::value;
};
template<int N>
struct IsPrimeHelper<N, 1> {
static constexpr bool value = true;
};
template<int N>
struct IsPrime {
static constexpr bool value = IsPrimeHelper<N, N - 1>::value;
};
// IsPrime<17>::value == true
6. 最大公約数
template<int A, int B>
struct GCD {
static constexpr int value = GCD<B, A % B>::value;
};
template<int A>
struct GCD<A, 0> {
static constexpr int value = A;
};
// GCD<24, 36>::value == 12
7. 累乗
template<int Base, int Exp>
struct Power {
static constexpr int value = Base * Power<Base, Exp - 1>::value;
};
template<int Base>
struct Power<Base, 0> {
static constexpr int value = 1;
};
// Power<2, 10>::value == 1024
8. 可変長テンプレートの再帰処理
// 合計
template<int... Ns>
struct Sum;
template<>
struct Sum<> {
static constexpr int value = 0;
};
template<int First, int... Rest>
struct Sum<First, Rest...> {
static constexpr int value = First + Sum<Rest...>::value;
};
// Sum<1, 2, 3, 4, 5>::value == 15
// 最大値
template<int... Ns>
struct Max;
template<int N>
struct Max<N> {
static constexpr int value = N;
};
template<int First, int Second, int... Rest>
struct Max<First, Second, Rest...> {
static constexpr int value =
Max<(First > Second ? First : Second), Rest...>::value;
};
// Max<3, 7, 2, 9, 1>::value == 9
9. 型変換ユーティリティ
template<typename T>
struct RemovePointer {
using type = T;
};
template<typename T>
struct RemovePointer<T*> {
using type = T;
};
template<typename T>
using RemovePointer_t = typename RemovePointer<T>::type;
// RemovePointer_t<int*> == int
実行結果
=== 階乗 ===
5! = 120
10! = 3628800
=== フィボナッチ ===
Fib(10) = 55
Fib(15) = 610
=== 型リスト ===
TypeList size: 4
Front type is int: 1
=== 素数判定 ===
IsPrime<7>: 1
IsPrime<12>: 0
IsPrime<17>: 1
=== 最大公約数 ===
GCD<24, 36>: 12
GCD<48, 18>: 6
=== 累乗 ===
2^10 = 1024
3^5 = 243
=== 合計・最大 ===
Sum<1,2,3,4,5>: 15
Max<3,7,2,9,1>: 9
TMPの利点
- ゼロコストの抽象化: 全てコンパイル時に計算される
- 型安全: コンパイル時に型チェック
- 最適化: ランタイムコストなし
現代C++では
C++11以降のconstexprやC++20のconceptsにより、TMPはより読みやすく書けるようになりました。
// 現代的な書き方
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
まとめ
TMPはC++の強力な機能ですが、可読性が低下しがちです。C++11以降ではconstexprを優先し、TMPは型レベルの操作に使うのが良いでしょう。