C++: テンプレートメタプログラミング
最終更新:2026-08-31
レッスン46では、スマートポインタについて学びました。
ここでは、C++の「黒魔法」——テンプレートメタプログラミングについて学びます。
テンプレートメタプログラミングを理解することは、C++上級者への道です。
1. テンプレートメタプログラミングとは
(1) 1.1 基本的な概念
テンプレートメタプログラミング(Template Metaprogramming, TMP)はコンパイル時に計算を行うテクニックです。
たとえ話:
- 通常のプログラム:実行時計算(実行速度に依存)
- TMP:コンパイル時計算(コンパイル時間は長いが、実行は高速)
(2) 1.2 なぜTMPを使う?
| 利点 | 説明 |
|---|---|
| パフォーマンス | コンパイル時計算、実行時コストゼロ |
| 型安全 | コンパイル時に型チェック |
| 柔軟性 | コードを自動生成 |
2. 基本的なテクニック
(1) 2.1 コンパイル時定数
▶ サンプル 1:コンパイル時計算(難易度 ⭐⭐)
CPP
#include <iostream>
// 再帰で階乗を計算
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N-1>::value;
};
// 終了条件
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
int main() {
std::cout << Factorial<5>::value << std::endl; // 120(コンパイル時計算)
return 0;
}
(2) 2.2 条件分岐
▶ サンプル:型による条件分岐(難易度 ⭐⭐⭐)
CPP
#include <iostream>
#include <type_traits>
// ポインタ型かどうか判定
template<typename T>
void printType(T value) {
if constexpr (std::is_pointer_v<T>) {
std::cout << "ポインタ型:" << *value << std::endl;
} else {
std::cout << "値型:" << value << std::endl;
}
}
int main() {
int x = 10;
printType(x); // 値型:10
printType(&x); // ポインタ型:10
return 0;
}
3. SFINAE
(1) 3.1 SFINAEとは?
SFINAE(Substitution Failure Is Not An Error):テンプレート引数の置換失敗はエラーではなく、他のオーバーロードを試す。
(2) 3.2 例:コンテナ判定(難易度 ⭐⭐⭐)
CPP
#include <iostream>
#include <vector>
#include <type_traits>
// コンテナかどうか判定(has begin())
template<typename T, typename = void>
struct IsContainer : std::false_type {};
template<typename T>
struct IsContainer<T, std::void_t<decltype(std::declval<T>().begin())>>
: std::true_type {};
int main() {
std::cout << std::boolalpha;
std::cout << "vectorはコンテナ:" << IsContainer<std::vector<int>>::value << std::endl; // true
std::cout << "intはコンテナ:" << IsContainer<int>::value << std::endl; // false
return 0;
}
4. 型トレイト
(1) 4.1 基本的な型トレイト
C++は <type_traits> で多くの型トレイトを提供します。
| 型トレイト | 説明 |
|---|---|
std::is_integral<T> |
整数型か? |
std::is_pointer<T> |
ポインタか? |
std::is_class<T> |
クラスか? |
std::is_same<T, U> |
TとUは同じ型か? |
std::remove_pointer<T> |
ポインタを除去 |
std::add_pointer<T> |
ポインタを追加 |
(2) 4.2 例:型判定(難易度 ⭐⭐)
CPP
#include <iostream>
#include <type_traits>
template<typename T>
void checkType() {
std::cout << "is_integral: " << std::is_integral_v<T> << std::endl;
std::cout << "is_pointer: " << std::is_pointer_v<T> << std::endl;
std::cout << "is_class: " << std::is_class_v<T> << std::endl;
}
int main() {
std::cout << "--- int ---" << std::endl;
checkType<int>();
std::cout << "--- int* ---" << std::endl;
checkType<int*>();
return 0;
}
5. C++17の新機能
(1) 5.1 畳み込み式
畳み込み式(Fold Expression)で可変長テンプレートパラメータを簡潔に処理できます。
▶ サンプル:可変長引数の和(難易度 ⭐⭐)
CPP
#include <iostream>
// C++17畳み込み式
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 右畳み込み
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 15
return 0;
}
(2) 5.2 if constexpr
if constexpr でコンパイル時条件分岐。
CPP
template<typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "整数:" << value << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "浮動小数点:" << value << std::endl;
} else {
std::cout << "その他" << std::endl;
}
}
6. 実践:コンパイル時文字列処理
▶ サンプル 1:コンパイル時文字列長(難易度 ⭐⭐⭐)
CPP
#include <iostream>
constexpr size_t strLength(const char* str) {
size_t len = 0;
while (str[len] != '\0') {
len++;
}
return len;
}
int main() {
constexpr size_t len = strLength("Hello"); // コンパイル時計算
std::cout << "長さ:" << len << std::endl; // 5
return 0;
}
▶ サンプル 2:型リスト処理(難易度 ⭐⭐⭐)
CPP
#include <iostream>
#include <type_traits>
// 型リスト
template<typename... Types>
struct TypeList {};
// 型リストの長さ
template<typename List>
struct Length;
template<typename... Types>
struct Length<TypeList<Types...>> {
static constexpr size_t value = sizeof...(Types);
};
int main() {
using MyList = TypeList<int, double, char>;
std::cout << "型の数:" << Length<MyList>::value << std::endl; // 3
return 0;
}
▶ サンプル 3:型トレイトの使用(難易度 ⭐⭐)
CPP
#include <iostream>
#include <type_traits>
#include <string>
template<typename T>
void analyzeType() {
std::cout << "型の分析:" << std::endl;
std::cout << " is_integral: " << std::is_integral_v<T> << std::endl;
std::cout << " is_floating_point: " << std::is_floating_point_v<T> << std::endl;
std::cout << " is_pointer: " << std::is_pointer_v<T> << std::endl;
std::cout << " is_reference: " << std::is_reference_v<T> << std::endl;
std::cout << " is_class: " << std::is_class_v<T> << std::endl;
}
int main() {
std::cout << "=== int ===" << std::endl;
analyzeType<int>();
std::cout << "\n=== double ===" << std::endl;
analyzeType<double>();
std::cout << "\n=== std::string ===" << std::endl;
analyzeType<std::string>();
return 0;
}
出力:
TEXT 📖 参照専用=== int === 型の分析: is_integral: 1 is_floating_point: 0 is_pointer: 0 is_reference: 0 is_class: 0 === double === 型の分析: is_integral: 0 is_floating_point: 1 is_pointer: 0 is_reference: 0 is_class: 0 === std::string === 型の分析: is_integral: 0 is_floating_point: 0 is_pointer: 0 is_reference: 0 is_class: 1
❓ よくある質問
Q:TMPの学習難易度は? A: 高いです。テンプレート、型トレイト、SFINAEなどを理解する必要があります。
Q:TMPのデバッグ方法は? A:- エラーメッセージを読む(コンパイラが出力)
static_assertで型チェック- シンプルな例から始める
Q:いつTMPを使う? A:- ライブラリ開発
- パフォーマンス最適化(コンパイル時計算)
- 型安全なAPI設計
📖 まとめ
| トピック | 要点 |
|---|---|
| コンパイル時計算 | テンプレートで計算 |
| SFINAE | 置換失敗はエラーではない |
| 型トレイト | <type_traits> で型判定 |
| 畳み込み式 | C++17可変長テンプレート処理 |
| if constexpr | C++17コンパイル時条件分岐 |
📝 練習問題
-
初級(難易度 ⭐): テンプレートメタ関数を書き、コンパイル時にNの二乗を計算(例:
Square<5>::value→ 25)。 -
中級(難易度 ⭐⭐):
std::is_integralなどの型トレイトを使って、汎用関数を書き、整数型・浮動小数点型・クラス型で異なる処理。 -
上級(難易度 ⭐⭐⭐): SFINAEを使って「呼び出し可能オブジェクトかどうか」判定するメタ関数を実装(
operator()の存在を検出)。
- テンプレートメタプログラミング:コンパイル時計算
- SFINAE:置換失敗はエラーではない
<type_traits>:型判定ユーティリティ- 畳み込み式:可変長テンプレートの簡潔な処理
if constexpr:コンパイル時条件分岐
次のレッスン:C++モダン機能(#48)