第1章:C++テンプレート
はじめに
CPP Module 07では、C++のテンプレートを学びます。テンプレートはジェネリックプログラミングの基盤であり、型に依存しない汎用的なコードを書くことを可能にします。
---
1. テンプレートの歴史
1.1 ジェネリックプログラミングの起源
David MusserとAlexander Stepanovは、1970年代後半からジェネリックプログラミングの概念を発展させました。彼らの研究は、後にC++のSTL(Standard Template Library)の基盤となりました。
Stepanovの設計哲学: > 「アルゴリズムは、可能な限り一般的であるべきである」
1.2 C++テンプレートの導入
1991年、Bjarne StroustrupはC++にテンプレートを追加しました。当初の目的は:
- 型安全なコンテナの実現
- コードの再利用性向上
- マクロの代替
1.3 Template Metaprogramming
テンプレートはコンパイル時に処理されるため、テンプレートメタプログラミングという技法が発展しました。これにより、コンパイル時に計算や型変換を行うことが可能になりました。
---
2. 関数テンプレート
2.1 基本構文
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
// 使用
int i = max(1, 2); // max<int>が生成
double d = max(1.5, 2.5); // max<double>が生成
2.2 型推論
コンパイラは引数から型を推論できます:
template <typename T>
T add(T a, T b) {
return a + b;
}
int result = add(1, 2); // T = int
double result = add(1.5, 2.5); // T = double
// 明示的に型を指定
auto result = add<double>(1, 2);
2.3 複数のテンプレートパラメータ
template <typename T, typename U>
T cast(U value) {
return static_cast<T>(value);
}
int i = cast<int>(3.14); // T = int, U = double
2.4 テンプレートのインスタンス化
テンプレートは使用時に具体的な型でインスタンス化されます:
template <typename T>
void print(T value) {
std::cout << value << std::endl;
}
print(42); // print<int>がインスタンス化
print(3.14); // print<double>がインスタンス化
print("hello"); // print<const char*>がインスタンス化
---
3. クラステンプレート
3.1 基本構文
template <typename T>
class Container {
private:
T* _elements;
size_t _size;
public:
Container(size_t size);
~Container();
T& operator[](size_t index);
const T& operator[](size_t index) const;
size_t size() const;
};
3.2 メソッドの定義
template <typename T>
Container<T>::Container(size_t size) : _size(size) {
_elements = new T[size];
}
template <typename T>
Container<T>::~Container() {
delete[] _elements;
}
template <typename T>
T& Container<T>::operator[](size_t index) {
return _elements[index];
}
3.3 使用例
Container<int> intContainer(10);
Container<std::string> strContainer(5);
intContainer[0] = 42;
strContainer[0] = "Hello";
---
4. テンプレートとヘッダファイル
4.1 なぜヘッダに実装を書くのか
テンプレートはコンパイル時にインスタンス化されるため、コンパイラは使用時点で完全な定義を必要とします:
// 間違い: template.hpp
template <typename T>
void func(T value);
// template.cpp
template <typename T>
void func(T value) {
std::cout << value << std::endl;
}
// main.cpp
#include "template.hpp"
func(42); // リンクエラー!
4.2 正しい方法
方法1: ヘッダに全て書く
// template.hpp
template <typename T>
void func(T value) {
std::cout << value << std::endl;
}
方法2: 明示的インスタンス化
// template.cpp
template void func<int>(int);
template void func<double>(double);
4.3 .tppファイル
一般的な慣習として、テンプレート実装を.tppファイルに書き、ヘッダの最後でインクルードします:
// template.hpp
template <typename T>
class Container { ... };
#include "template.tpp"
// template.tpp
template <typename T>
Container<T>::Container() { ... }
---
5. テンプレートパラメータの制約
5.1 暗黙的な制約
テンプレートは、使用される操作をサポートする型でのみ動作します:
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b; // operator>が必要
}
// OK: intは>をサポート
max(1, 2);
// エラー: 独自クラスで>が未定義
class Foo {};
max(Foo(), Foo()); // コンパイルエラー
5.2 カスタム型での使用
class Number {
int value;
public:
Number(int v) : value(v) {}
bool operator>(const Number& other) const {
return value > other.value;
}
};
max(Number(1), Number(2)); // OK
5.3 SFINAE
SFINAE(Substitution Failure Is Not An Error)は、テンプレートの選択に使われる技法です:
template <typename T>
typename T::value_type sum(const T& container) {
// T::value_typeが存在する型でのみ有効
}
---
6. 42課題での実装ポイント
6.1 ex00: swap, min, max
template <typename T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
template <typename T>
T min(T a, T b) {
return (a < b) ? a : b;
}
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
6.2 ex01: iter
template <typename T>
void iter(T* array, size_t length, void (*func)(T&)) {
for (size_t i = 0; i < length; i++) {
func(array[i]);
}
}
// const版
template <typename T>
void iter(const T* array, size_t length, void (*func)(const T&)) {
for (size_t i = 0; i < length; i++) {
func(array[i]);
}
}
6.3 ex02: Array
template <typename T>
class Array {
private:
T* _elements;
unsigned int _size;
public:
Array();
Array(unsigned int n);
Array(const Array& other);
Array& operator=(const Array& other);
~Array();
T& operator[](unsigned int index);
const T& operator[](unsigned int index) const;
unsigned int size() const;
class OutOfBoundsException : public std::exception {
const char* what() const throw();
};
};
---
7. テンプレートの利点と欠点
7.1 利点
- 型安全: マクロと違い、型チェックが行われる
- コード再利用: 1つの定義で複数の型に対応
- パフォーマンス: インライン展開により高速
- 表現力: 複雑な型関係を表現可能
- コンパイル時間: インスタンス化に時間がかかる
- エラーメッセージ: 複雑で理解しにくい
- コードサイズ: 各インスタンスで別のコードが生成
- デバッグ困難: インスタンス化後のコードが見えにくい
7.2 欠点
---
8. C++11以降のテンプレート機能
8.1 可変引数テンプレート
template <typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl;
}
print(1, " ", 2.5, " ", "hello");
8.2 autoとdecltype
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
8.3 コンセプト(C++20)
template <typename T>
concept Comparable = requires(T a, T b) {
{ a < b } -> std::convertible_to<bool>;
};
template <Comparable T>
T min(T a, T b) {
return (a < b) ? a : b;
}
---
まとめ
本章で学んだこと:
次章では、各演習の詳細な実装を解説します。