テンプレートは、C++ のジェネリックな強力な機能であり、クラス、関数、変数に適用することができます。テンプレートの存在により、コーダーは型に依存しないコードを書くことができ、コンパイル時に一般的なクラス、関数、変数を生成することができます。
テンプレートの基本形式#
テンプレートは、任意の型のデータを操作できるクラスや関数を生成するためのブループリントを定義します。
テンプレートの基本形式#
一般的な関数テンプレートを例にとると、テンプレートの定義は次のようになります:
template <typename T>
T func(T param){
// 何か処理...
return param;
}
上記のコードは、単一の型パラメータ T を持つ関数テンプレートであり、パラメータと戻り値の両方が T 型です。
- キーワード
template
は、テンプレートの定義を示します。 - キーワード
typename
は、パラメータ型のプレースホルダーであり、T
はテンプレートパラメータです。コンパイル時に、関数の呼び出しに基づいてT
のインスタンスの型を具体的なパラメータ型に置き換えるプロセスが行われます。このプロセスを「テンプレートのインスタンス化」と呼びます。
呼び出し時には、通常の関数のように呼び出します:
int i = func(42);
クラステンプレート#
template <typename T>
class Array {
private:
T* data;
size_t size;
public:
explicit Array(size_t size): size(size), data(new T[size]) {}
~Array() {delete[] data;}
T& operator[](size_t index) {return data[index];}
}
関数テンプレート#
template <typename T>
T max(T a, T b) {
return (a > b)? a:b;
}
変数テンプレート#
変数テンプレートは、C++14 で導入された新機能であり、任意の型の静的変数を生成することができます。
template <typename T>
constexpr T pi = T(3.1415)
テンプレートの型パラメータ#
テンプレートは、パラメータの数に制限はありません。これは通常の関数と同じです。
template <typename T, typename U, typename V>
任意の数の型パラメータを持つテンプレートを定義することができます。
template<typename... Args> class vtclass;
template<typename... Args>
void func(Arguments... args)
C++17 以降、自動推論される型を使用することができます。auto
がテンプレートパラメータの位置で使用される場合、それは非型テンプレートパラメータ(non-type template parameter)として任意の型のパラメータを受け入れることができます。これには整数、文字、ブール値などが含まれます。このようなテンプレートは、自動型推論される非型テンプレートパラメータ(auto-typed non-type template parameters)と呼ばれます。
template <auto x> constexpr auto constant = x;
テンプレートパラメータ<auto x>
は、constant
が任意の型の定数式をパラメータとして受け入れることを示しています。constant
の値は、それに渡されたパラメータx
の値になります。
デフォルトテンプレート変数#
デフォルトテンプレート変数の宣言は、通常の関数と同じです:
template <typename T = int>
void func(T factor){
// 何か処理
}
func<>(42);
テンプレートの特殊化#
テンプレートは強力なジェネリック機能を提供しますが、テンプレートを定義する際には、すべてのケースに対して重複するコードを書く必要はありません。多くの場合、パラメータの型によってのみ実行パスが異なる場合には、テンプレートの特殊化が必要です。
template <typename T, typename U>
class MyClass{}; // テンプレートの定義
template <typename T>
class MyClass<T, int>{} // テンプレートの特殊化
MyClass<int, string> myClass1; // 元のテンプレート
MyClass<int, int> myClass2; // テンプレートの特殊化
クラステンプレート#
クラステンプレートのメンバ関数#
クラステンプレート内で関数を定義することもできますが、クラスの外部で定義する場合は、クラステンプレートのテンプレート宣言部分を指定する必要があります。
template<class T, int i>
class MyStack {
// メンバーとメソッドの宣言
};
template<class T, int i>
void MyStack<T, i>::push(const T item) {
// 何か処理
}
template<class T, int i>
T& MyStack<T, i>::pop() {
// 何か処理
}
より一般的な方法は、クラステンプレート内で直接テンプレートメンバ関数を定義することです。この方法は、コンパイラがインライン処理を行い、効率を向上させるのに役立ちます。同様に、コードを簡素化することもできます。
一般的な非テンプレートの関数やクラスでは、通常、ヘッダーファイルで宣言し、ソースファイルで実装しますが、テンプレートプログラミングでは、テンプレートがコンパイル時に完全なテンプレート定義にアクセスできるようにするため、テンプレートの定義と宣言を両方ヘッダーファイルに直接配置することが最適なプラクティスです。そうしないと、テンプレートのインスタンス化が手動で行われない限り、リンク時にテンプレートインスタンスの定義が見つからないため、リンクエラーが発生する可能性があります。
複雑な大規模なテンプレートライブラリの場合、テンプレートの実装コードを
.ipp
ファイルまたは.tpp
ファイルに配置し、ヘッダーファイルの最後にインクルードすることがよくあります。
// MyTemplate.h
template<typename T>
class MyTemplate {
public:
void func(T value);
};
#include "MyTemplate.tpp"
// MyTemplate.tpp, 実際のテンプレートの実装を含む
template<typename T>
void MyTemplate<T>::doSomething(T value) {
// メソッドの実装
}
ネストされたクラステンプレート#
テンプレートはネストすることができます。
#include <iostream>
template<class T>
class A{
public:
template <class U>
class B{
private:
U u; // ポインタではなくU型の値を使用することで、例を簡略化しています
public:
B() {} // コンストラクタの実装
U& Value() { return u; } // U型の参照を返す
void print() { std::cout << u << std::endl; } // U型の値を出力する
~B() {} // デストラクタの実装
};
B<int> b;
public:
A(T t) { b.Value() = t; }
void print() { b.print(); } // B<int>のprintを呼び出す
};
// 外部でネストされたクラステンプレートのメンバ関数を定義する場合の構文の例
template<class T>
template<class U>
A<T>::B<U>::B() {
// コンストラクタの具体的な実装
}
template<class T>
template<class U>
U& A<T>::B<U>::Value() {
// この関数はU型の参照を返すことができますが、ここでは単なる例です
return u;
}
int main() {
A<int> a(42); // Aクラスのインスタンスを作成
a.print(); // 42を出力
}
関数テンプレート#
関数テンプレートのインスタンス化#
明示的な指定または推論によって関数をインスタンス化することができます。
template<class T> void f(T) { }
template void f<int> (int);
template void f(char);
extern
キーワードを使用して、重複するコードの生成(インスタンス化)を回避し、コードを細かく制御することもできます。
template<typename T, std::size_t N>
class MyClass {};
exterm template MyClass<int, 4>::MyClass(void);