第8章:テンプレート

はじめに

テンプレートはC++の最も強力な機能の一つであり、ジェネリックプログラミングを可能にします。Alexander Stepanovは、STL(Standard Template Library)の設計を通じて、テンプレートの真の力を示しました。

本章では、テンプレートの基礎から、関数テンプレート、クラステンプレート、そして高度なテンプレート技法まで学びます。

---

1. テンプレートの歴史と概念

1.1 ジェネリックプログラミングの起源

Alexander Stepanovは、1970年代からジェネリックプログラミングの概念を研究していました:

> "Generic programming is about generalizing software components so that they can easily be reused in a wide variety of situations." > > (ジェネリックプログラミングとは、ソフトウェアコンポーネントを汎用化し、様々な状況で容易に再利用できるようにすることである)

テンプレートは以下の問題を解決します:

  • コードの重複: 同じアルゴリズムを異なる型で使いたい
  • 型安全性: void*のような型安全でない解決策を避けたい
  • パフォーマンス: 実行時のオーバーヘッドを避けたい
  • 1.2 マクロとの比較

    C言語ではマクロでジェネリック的なコードを書いていました:

    /* C言語のマクロ */
    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    
    int x = MAX(3, 5);        // OK
    int y = MAX(3++, 5);      // 危険!3++が複数回評価される可能性
    

    C++のテンプレートはより安全:

    // C++のテンプレート
    template <typename T>
    T max(T a, T b) {
        return (a > b) ? a : b;
    }
    
    int x = max(3, 5);        // OK
    int y = max(3, 5);        // 安全:引数は一度だけ評価
    

    ---

    2. 関数テンプレート

    2.1 基本構文

    // テンプレート宣言
    template <typename T>
    T add(T a, T b) {
        return a + b;
    }
    
    // typenameの代わりにclassを使うことも可能(意味は同じ)
    template <class T>
    T subtract(T a, T b) {
        return a - b;
    }
    
    int main() {
        std::cout << add(3, 5) << std::endl;        // 8 (int)
        std::cout << add(3.14, 2.71) << std::endl;  // 5.85 (double)
        std::cout << add<float>(3, 5) << std::endl; // 8 (明示的にfloat)
        return 0;
    }
    

    2.2 テンプレート引数の推論

    コンパイラは引数から型を推論します:

    template <typename T>
    void print(T value) {
        std::cout << value << std::endl;
    }
    
    int main() {
        print(42);          // T = int と推論
        print(3.14);        // T = double と推論
        print("Hello");     // T = const char* と推論
    
        // 明示的な型指定
        print<std::string>("Hello");  // T = std::string
        return 0;
    }
    

    2.3 複数のテンプレートパラメータ

    template <typename T, typename U>
    auto multiply(T a, U b) -> decltype(a * b) {
        return a * b;
    }
    
    // C++14以降では戻り値型の推論が可能
    template <typename T, typename U>
    auto divide(T a, U b) {
        return a / b;
    }
    
    int main() {
        std::cout << multiply(3, 4.5) << std::endl;    // 13.5 (int * double = double)
        std::cout << divide(10, 3) << std::endl;       // 3 (int / int = int)
        std::cout << divide(10.0, 3) << std::endl;     // 3.333... (double / int = double)
        return 0;
    }
    

    2.4 非型テンプレートパラメータ

    型以外の値もテンプレートパラメータにできます:

    template <typename T, int Size>
    class Array {
        T data[Size];
    public:
        int size() const { return Size; }
        T& operator[](int index) { return data[index]; }
    };
    
    template <int N>
    int factorial() {
        return N * factorial<N - 1>();
    }
    
    template <>
    int factorial<0>() {
        return 1;
    }
    
    int main() {
        Array<int, 10> arr;
        std::cout << "Size: " << arr.size() << std::endl;  // 10
    
        std::cout << "5! = " << factorial<5>() << std::endl;  // 120
        return 0;
    }
    

    2.5 関数テンプレートのオーバーロード

    // 汎用版
    template <typename T>
    void process(T value) {
        std::cout << "Generic: " << value << std::endl;
    }
    
    // ポインタ用の特殊化
    template <typename T>
    void process(T* ptr) {
        std::cout << "Pointer: " << *ptr << std::endl;
    }
    
    // 非テンプレート版(最優先)
    void process(int value) {
        std::cout << "Int specific: " << value << std::endl;
    }
    
    int main() {
        int x = 42;
        process(x);       // Int specific: 42
        process(&x);      // Pointer: 42
        process(3.14);    // Generic: 3.14
        process("Hello"); // Generic: Hello
        return 0;
    }
    

    ---

    3. クラステンプレート

    3.1 基本構文

    template <typename T>
    class Container {
    private:
        T value;
    
    public:
        Container(T v) : value(v) {}
    
        T getValue() const { return value; }
        void setValue(T v) { value = v; }
    };
    
    int main() {
        Container<int> intContainer(42);
        Container<std::string> strContainer("Hello");
    
        std::cout << intContainer.getValue() << std::endl;  // 42
        std::cout << strContainer.getValue() << std::endl;  // Hello
        return 0;
    }
    

    3.2 メンバ関数の外部定義

    template <typename T>
    class Stack {
    private:
        std::vector<T> elements;
    
    public:
        void push(const T& element);
        T pop();
        bool empty() const;
    };
    
    // メンバ関数の外部定義
    template <typename T>
    void Stack<T>::push(const T& element) {
        elements.push_back(element);
    }
    
    template <typename T>
    T Stack<T>::pop() {
        if (elements.empty()) {
            throw std::runtime_error("Stack is empty");
        }
        T top = elements.back();
        elements.pop_back();
        return top;
    }
    
    template <typename T>
    bool Stack<T>::empty() const {
        return elements.empty();
    }
    

    3.3 テンプレートのデフォルト引数

    template <typename T = int, int Size = 10>
    class FixedArray {
        T data[Size];
    public:
        int size() const { return Size; }
        T& operator[](int i) { return data[i]; }
    };
    
    int main() {
        FixedArray<> arr1;           // int, 10
        FixedArray<double> arr2;     // double, 10
        FixedArray<char, 100> arr3;  // char, 100
        return 0;
    }
    

    3.4 メンバテンプレート

    クラス内にテンプレートメンバを持つことも可能:

    class Converter {
    public:
        template <typename T, typename U>
        T convert(U value) {
            return static_cast<T>(value);
        }
    };
    
    template <typename T>
    class Container {
        T value;
    public:
        Container(T v) : value(v) {}
    
        // 異なる型のContainerからのコピーを許可
        template <typename U>
        Container(const Container<U>& other) : value(other.getValue()) {}
    
        T getValue() const { return value; }
    };
    
    int main() {
        Converter conv;
        double d = conv.convert<double>(42);  // 42.0
    
        Container<double> dc(3.14);
        Container<int> ic(dc);  // double → int への変換
        std::cout << ic.getValue() << std::endl;  // 3
        return 0;
    }
    

    ---

    4. テンプレートの特殊化

    4.1 完全特殊化

    特定の型に対して異なる実装を提供:

    // プライマリテンプレート
    template <typename T>
    class Storage {
        T value;
    public:
        Storage(T v) : value(v) {}
        void print() const {
            std::cout << "Generic: " << value << std::endl;
        }
    };
    
    // bool型の完全特殊化
    template <>
    class Storage<bool> {
        bool value;
    public:
        Storage(bool v) : value(v) {}
        void print() const {
            std::cout << "Boolean: " << (value ? "true" : "false") << std::endl;
        }
    };
    
    // char*の完全特殊化
    template <>
    class Storage<const char*> {
        const char* value;
    public:
        Storage(const char* v) : value(v) {}
        void print() const {
            std::cout << "C-string: \"" << value << "\"" << std::endl;
        }
    };
    
    int main() {
        Storage<int> si(42);
        Storage<bool> sb(true);
        Storage<const char*> sc("Hello");
    
        si.print();  // Generic: 42
        sb.print();  // Boolean: true
        sc.print();  // C-string: "Hello"
        return 0;
    }
    

    4.2 部分特殊化

    一部のパラメータを特殊化:

    // プライマリテンプレート
    template <typename T, typename U>
    class Pair {
    public:
        void describe() const {
            std::cout << "Generic pair" << std::endl;
        }
    };
    
    // 両方が同じ型の場合の部分特殊化
    template <typename T>
    class Pair<T, T> {
    public:
        void describe() const {
            std::cout << "Same type pair" << std::endl;
        }
    };
    
    // 2つ目がポインタの場合の部分特殊化
    template <typename T, typename U>
    class Pair<T, U*> {
    public:
        void describe() const {
            std::cout << "Second is pointer" << std::endl;
        }
    };
    
    // 両方がポインタの場合
    template <typename T, typename U>
    class Pair<T*, U*> {
    public:
        void describe() const {
            std::cout << "Both are pointers" << std::endl;
        }
    };
    
    int main() {
        Pair<int, double> p1;
        Pair<int, int> p2;
        Pair<int, double*> p3;
        Pair<int*, double*> p4;
    
        p1.describe();  // Generic pair
        p2.describe();  // Same type pair
        p3.describe();  // Second is pointer
        p4.describe();  // Both are pointers
        return 0;
    }
    

    4.3 関数テンプレートの特殊化

    // プライマリテンプレート
    template <typename T>
    void print(T value) {
        std::cout << value << std::endl;
    }
    
    // 完全特殊化
    template <>
    void print<bool>(bool value) {
        std::cout << (value ? "true" : "false") << std::endl;
    }
    
    // 注意: 関数テンプレートには部分特殊化がない
    // 代わりにオーバーロードを使用
    
    // ポインタ用のオーバーロード
    template <typename T>
    void print(T* ptr) {
        std::cout << "Pointer to: " << *ptr << std::endl;
    }
    

    ---

    5. SFINAE(Substitution Failure Is Not An Error)

    5.1 SFINAEの概念

    テンプレート引数の置換が失敗しても、それはエラーではなく、そのテンプレートが候補から除外されるだけです:

    #include <type_traits>
    
    // std::enable_ifを使ったSFINAE
    template <typename T>
    typename std::enable_if<std::is_integral<T>::value, void>::type
    process(T value) {
        std::cout << "Integer: " << value << std::endl;
    }
    
    template <typename T>
    typename std::enable_if<std::is_floating_point<T>::value, void>::type
    process(T value) {
        std::cout << "Floating point: " << value << std::endl;
    }
    
    int main() {
        process(42);      // Integer: 42
        process(3.14);    // Floating point: 3.14
        // process("Hi"); // エラー:どちらのテンプレートも適用不可
        return 0;
    }
    

    5.2 型特性(Type Traits)

    C++11以降、ヘッダで多くの型特性が提供されています:

    #include <type_traits>
    
    template <typename T>
    void checkType() {
        std::cout << "is_integral: " << std::is_integral<T>::value << std::endl;
        std::cout << "is_floating_point: " << std::is_floating_point<T>::value << std::endl;
        std::cout << "is_pointer: " << std::is_pointer<T>::value << std::endl;
        std::cout << "is_const: " << std::is_const<T>::value << std::endl;
    }
    
    // 条件付きコンパイル
    template <typename T>
    void safeDelete(T* ptr) {
        static_assert(std::is_pointer<T*>::value, "Must be a pointer");
        delete ptr;
    }
    
    int main() {
        checkType<int>();
        // is_integral: 1
        // is_floating_point: 0
        // is_pointer: 0
        // is_const: 0
    
        checkType<const double>();
        // is_integral: 0
        // is_floating_point: 1
        // is_pointer: 0
        // is_const: 1
    
        return 0;
    }
    

    5.3 C++17のif constexpr

    コンパイル時条件分岐:

    template <typename T>
    void process(T value) {
        if constexpr (std::is_integral_v<T>) {
            std::cout << "Integer: " << value << std::endl;
        } else if constexpr (std::is_floating_point_v<T>) {
            std::cout << "Float: " << value << std::endl;
        } else {
            std::cout << "Other: " << value << std::endl;
        }
    }
    

    ---

    6. 可変引数テンプレート(C++11)

    6.1 基本構文

    // 可変引数テンプレート
    template <typename... Args>
    void print(Args... args) {
        // sizeof...で引数の数を取得
        std::cout << "Number of arguments: " << sizeof...(args) << std::endl;
    }
    
    int main() {
        print();                    // 0
        print(1);                   // 1
        print(1, 2.0, "three");     // 3
        return 0;
    }
    

    6.2 再帰的展開

    // 基底ケース
    void printAll() {
        std::cout << std::endl;
    }
    
    // 再帰ケース
    template <typename T, typename... Rest>
    void printAll(T first, Rest... rest) {
        std::cout << first << " ";
        printAll(rest...);  // 残りの引数で再帰呼び出し
    }
    
    int main() {
        printAll(1, 2.5, "Hello", 'X');
        // 出力: 1 2.5 Hello X
        return 0;
    }
    

    6.3 フォールド式(C++17)

    // 加算フォールド
    template <typename... Args>
    auto sum(Args... args) {
        return (args + ...);  // 右フォールド
    }
    
    // 出力フォールド
    template <typename... Args>
    void printAll(Args... args) {
        (std::cout << ... << args) << std::endl;  // 左フォールド
    }
    
    // 論理フォールド
    template <typename... Args>
    bool allTrue(Args... args) {
        return (args && ...);
    }
    
    int main() {
        std::cout << sum(1, 2, 3, 4, 5) << std::endl;  // 15
        printAll("Hello", " ", "World", "!");          // Hello World!
        std::cout << allTrue(true, true, false) << std::endl;  // 0
        return 0;
    }
    

    ---

    7. テンプレートメタプログラミング

    7.1 コンパイル時計算

    // コンパイル時フィボナッチ
    template <int N>
    struct Fibonacci {
        static const int value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;
    };
    
    template <>
    struct Fibonacci<0> {
        static const int value = 0;
    };
    
    template <>
    struct Fibonacci<1> {
        static const int value = 1;
    };
    
    // constexprを使った現代的なアプローチ(C++11以降)
    constexpr int fibonacci(int n) {
        if (n <= 1) return n;
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
    
    int main() {
        // コンパイル時に計算
        std::cout << Fibonacci<10>::value << std::endl;  // 55
        std::cout << fibonacci(10) << std::endl;         // 55
    
        // コンパイル時定数として使用
        constexpr int fib10 = fibonacci(10);
        int arr[fib10];  // 配列サイズとして使用可能
        return 0;
    }
    

    7.2 型リスト

    // 型リストの実装
    template <typename... Types>
    struct TypeList {};
    
    // 型リストの長さ
    template <typename List>
    struct Length;
    
    template <typename... Types>
    struct Length<TypeList<Types...>> {
        static const int value = sizeof...(Types);
    };
    
    // 最初の型を取得
    template <typename List>
    struct Front;
    
    template <typename Head, typename... Tail>
    struct Front<TypeList<Head, Tail...>> {
        using type = Head;
    };
    
    int main() {
        using MyList = TypeList<int, double, char>;
    
        std::cout << Length<MyList>::value << std::endl;  // 3
    
        static_assert(std::is_same_v<Front<MyList>::type, int>);
        return 0;
    }
    

    7.3 タグディスパッチ

    // タグ型
    struct InputIteratorTag {};
    struct RandomAccessIteratorTag : InputIteratorTag {};
    
    // イテレータカテゴリに応じた実装
    template <typename Iterator>
    void advanceImpl(Iterator& it, int n, InputIteratorTag) {
        std::cout << "Slow advance (input iterator)" << std::endl;
        while (n-- > 0) ++it;
    }
    
    template <typename Iterator>
    void advanceImpl(Iterator& it, int n, RandomAccessIteratorTag) {
        std::cout << "Fast advance (random access)" << std::endl;
        it += n;
    }
    
    template <typename Iterator>
    void advance(Iterator& it, int n) {
        advanceImpl(it, n, typename std::iterator_traits<Iterator>::iterator_category());
    }
    

    ---

    8. テンプレートのベストプラクティス

    8.1 ヘッダファイルでの定義

    テンプレートは通常、ヘッダファイルで完全に定義する必要があります:

    // my_template.hpp
    #ifndef MY_TEMPLATE_HPP
    #define MY_TEMPLATE_HPP
    
    template <typename T>
    class MyClass {
        T value;
    public:
        MyClass(T v) : value(v) {}
        T getValue() const { return value; }
        void setValue(T v) { value = v; }
    };
    
    // 関数テンプレートもヘッダで定義
    template <typename T>
    T maximum(T a, T b) {
        return (a > b) ? a : b;
    }
    
    #endif
    

    8.2 明示的インスタンス化

    コンパイル時間を短縮するため、明示的にインスタンス化できます:

    // my_template.hpp
    template <typename T>
    class MyClass {
        // 宣言のみ
        T getValue() const;
    };
    
    // my_template.cpp
    #include "my_template.hpp"
    
    template <typename T>
    T MyClass<T>::getValue() const {
        return value;
    }
    
    // 明示的インスタンス化
    template class MyClass<int>;
    template class MyClass<double>;
    template class MyClass<std::string>;
    

    8.3 static_assertによるエラーメッセージ

    template <typename T>
    class NumericContainer {
        static_assert(std::is_arithmetic<T>::value,
                      "T must be a numeric type");
        T value;
    public:
        NumericContainer(T v) : value(v) {}
    };
    
    int main() {
        NumericContainer<int> nc1(42);       // OK
        NumericContainer<double> nc2(3.14);  // OK
        // NumericContainer<std::string> nc3("Hi");  // コンパイルエラー
        return 0;
    }
    

    8.4 テンプレートのデバッグ

    // 型名を取得するヘルパー
    #include <typeinfo>
    #include <cxxabi.h>
    
    template <typename T>
    std::string typeName() {
        int status;
        char* demangled = abi::__cxa_demangle(typeid(T).name(), 0, 0, &status);
        std::string result(demangled);
        free(demangled);
        return result;
    }
    
    template <typename T>
    void debugType(T value) {
        std::cout << "Type: " << typeName<T>() << std::endl;
        std::cout << "Value: " << value << std::endl;
    }
    

    ---

    まとめ

    本章で学んだこと:

  • テンプレートの概念: ジェネリックプログラミング、Stepanovの貢献
  • 関数テンプレート: 型推論、オーバーロード
  • クラステンプレート: メンバ関数、メンバテンプレート
  • テンプレート特殊化: 完全特殊化、部分特殊化
  • SFINAE: enable_if、型特性
  • 可変引数テンプレート: パラメータパック、フォールド式
  • メタプログラミング: コンパイル時計算
  • 次章では、STL(Standard Template Library)について学びます。

    ---

    練習問題

  • 任意の型の配列を受け取り、その中の最大値と最小値を返す関数テンプレートを実装してください。
  • スタックを実装するクラステンプレートを作成してください。以下の操作をサポートすること:
- push - pop - top - empty - size

  • 2つの型が同じかどうかをコンパイル時に判定するメタ関数IsSameを実装してください:
template <typename T, typename U>
struct IsSame {
    // 実装
};

static_assert(IsSame<int, int>::value == true);
static_assert(IsSame<int, double>::value == false);

  • 可変引数テンプレートを使って、任意の数の引数の平均値を計算する関数を実装してください。