C++: テンプレートメタプログラミング

最終更新:2026-08-31

レッスン46では、スマートポインタについて学びました。

ここでは、C++の「黒魔法」——テンプレートメタプログラミングについて学びます。

テンプレートメタプログラミングを理解することは、C++上級者への道です。


1. テンプレートメタプログラミングとは

(1) 1.1 基本的な概念

テンプレートメタプログラミング(Template Metaprogramming, 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コンパイル時条件分岐

📝 練習問題

  1. 初級(難易度 ⭐): テンプレートメタ関数を書き、コンパイル時にNの二乗を計算(例:Square<5>::value → 25)。

  2. 中級(難易度 ⭐⭐): std::is_integral などの型トレイトを使って、汎用関数を書き、整数型・浮動小数点型・クラス型で異なる処理。

  3. 上級(難易度 ⭐⭐⭐): SFINAEを使って「呼び出し可能オブジェクトかどうか」判定するメタ関数を実装(operator() の存在を検出)。



次のレッスン:C++モダン機能(#48)

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%