C++プログラミングにおいて、テンプレートは非常に重要な概念であり、テンプレートを使うことで、汎用的なコードを書くことができます。
そこで本記事では、C++のテンプレートの基礎から応用までわかりやすく解説します。
テンプレートとは何か
C++におけるテンプレートとは、汎用的なデータ型や関数を定義するための仕組みです。具体的には、クラスや関数の定義時に、その中で使用されるデータ型や引数の型を決めずに、後から指定することができます。
テンプレートの基本的な使い方
C++におけるテンプレートは、汎用的なコードを書くための機能です。テンプレートを使用することで、同じような処理を行う関数やクラスを複数作成する必要がなくなります。
関数テンプレート
関数テンプレートは、引数の型に応じて異なる処理を行う関数を定義するために使用されます。以下は、2つの引数の和を計算する関数テンプレートの例です。
template <typename T>
T add(T a, T b) {
return a + b;
}
この関数テンプレートでは、<typename T>
という記述があります。これは、T
という型パラメーターを定義しています。T
は任意の型であることができます。例えば、以下のように呼び出すことができます。
int result1 = add(1, 2); // result1 = 3(int型関数として処理)
double result2 = add(1.5, 2.5); // result2 = 4.0(double型関数として処理)
このように、引数の型に応じて適切な処理が行われるため、同じような処理を行う関数を複数作成する必要がありません。
クラステンプレート
クラステンプレートは、異なるデータ型に対して同じような操作を行うクラスを定義するために使用されます。以下は、スタック(後入れ先出し)構造を実現するクラステンプレートの例です。
template <typename T>
class Stack {
private:
T data[100];
int top;
public:
Stack() : top(-1) {}
void push(T value) {
data[++top] = value;
}
T pop() {
return data[top--];
}
};
このクラステンプレートでは、<typename T>
という記述があります。これは、T
という型パラメーターを定義しています。例えば、以下のように呼び出すことができます。
Stack<int> stack1; // int型用のスタックオブジェクト生成
stack1.push(10);
stack1.push(20);
int value1 = stack1.pop(); // value1 = 20
int value2 = stack1.pop(); // value2 = 10
Stack<double> stack2; // double型用のスタックオブジェクト生成
stack2.push(3.14);
double value3 = stack2.pop(); // value3 = 3.14
このように、異なるデータ型でも同じ操作が可能です。
テンプレートの特殊化
テンプレートの特殊化とは、テンプレートを使用する際に、特定の型に対して別の処理を行うことができる機能です。
部分特殊化
部分特殊化は、テンプレート引数の一部を固定し、その他の引数に対して通常のテンプレート処理を行う方法です。
例えば、以下のような関数テンプレートがあったとします。
template <typename T1, typename T2>
void func(T1 arg1, T2 arg2) {
// 処理
}
この関数テンプレートに対して、T1がint型である場合だけ別の処理を行いたい場合、以下のように部分特殊化を使って実現できます。
//第二引数がint型だった場合のみ掛け算した結果を出力
template <typename T1>
void func(T1 arg1, int arg2) {
std::cout << (arg1 * arg2) << std::endl;
}
このような実装でfunc()
関数を使うとこのようになります。
#include <iostream>
template <typename T1, typename T2>
void func(T1 arg1, T2 arg2) {
std::cout << (arg1 + arg2) << std::endl;
}
//第二引数がint型だった場合のみ掛け算した結果を出力
template <typename T1>
void func(T1 arg1, int arg2) {
std::cout << (arg1 * arg2) << std::endl;
}
int main() {
func(2.5, 3.0);
func(2.5, 3);
}
5.5
7.5
部分特殊化を応用することで、特定のクラスが引数に渡された場合だけ処理を変えるといったことが可能になります。
完全特殊化
完全特殊化は、すべての引数が固定された状態で、特定の型に対して別の処理を行う方法です。
例えば、以下のような関数テンプレートがあったとします。
template <typename T>
void func(T arg) {
// 処理
}
この関数テンプレートに対して、Tがint型である場合だけ別の処理を行いたい場合、以下のように完全特殊化を使って実現できます。
template <>
void func(int arg) {
// 別の処理
}
テンプレートの制約
テンプレートを使用する際には、制約が必要です。制約とは、テンプレートが受け取る型に対して、どのような操作が可能であるかを明示することです。
C++20からは、コンセプトという機能が導入されました。コンセプトを使用することで、テンプレートの制約をより明確に表現することができます。
例えば、以下のような関数テンプレートがあります。
template <typename T>
void print(T value) {
std::cout << value << std::endl;
}
この関数テンプレートは、任意の型 T
を受け取ってその値を出力します。
しかし、このままでは T
に対して何らかの演算子オーバーロードが定義されていない場合でもコンパイルエラーになりません。
そこで、以下のようにコンセプトを使用して制約を設けます。
template <typename T>
concept Printable = requires(T t) {
std::cout << t;
};
template <Printable T>
void print(T value) {
std::cout << value << std::endl;
}
ここではPrintable
という名前のコンセプトを定義しました。
このコンセプトは、「任意の型 T
に対して std::cout << t;
という式が有効である(コンパイルエラーにならない)」という条件を満たす型に適用されます。
そして、関数テンプレート print()
の引数には Printable
コンセプトが適用されています。
これにより、演算子オーバーロードが定義されていない型を引数にした場合はコンパイルエラーとなります。
テンプレートの応用例
テンプレートは、C++において非常に強力な機能であり、多くの場面で活用されています。
ここでは、テンプレートの応用例として、STLのコンテナ、ラムダ式、CRTPについて解説します。
STLのコンテナ
STL(Standard Template Library)は、C++標準ライブラリの一部であり、汎用的なデータ構造やアルゴリズムを提供しています。その中でも特に重要な役割を果たすのが、コンテナです。
コンテナは、データを格納するためのオブジェクトであり、vectorやlistなどが代表的な例です。
これらのコンテナは全てテンプレートで実装されており、異なる型のデータを格納することが可能です。
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
for (auto i : v) {
std::cout << i << " ";
}
return 0;
}
上記の例では、int型を格納するvectorを作成し、for文で要素を順番に出力しています。このようにSTLのコンテナは非常に便利であり、多くの場面で活用されます。

ラムダ式
ラムダ式はC++11から導入された機能であり、「無名関数」を定義することが出来ます
。ラムダ式もまたテンプレートと組み合わせることが出来ます。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = { 1, 2, 3, 4, 5 };
auto is_even = [](int x) {return x % 2 == 0; };
int count = std::count_if(v.begin(), v.end(), is_even);
std::cout << count << std::endl;
return 0;
}
上記の例では、count_if関数を使用してv内にある偶数値をカウントしています。この際に、「xが偶数かどうか」を判定するラムダ式を記述して、カウント条件の処理を作成しています。

CRTP
CRTP(Curiously Recurring Template Pattern)は、「奇妙な再帰的テンプレートパターン」と呼ばれる設計パターンです。CRTPでは派生クラス自身が基底クラスと同じ型であるような継承関係を作成し、「静的ポリモーフィズム」を実現します。
テンプレートの仕組みを理解していないと実用が難しい応用テクニックです。全く理解できなくてもテンプレートの扱いで困ることはありません。
template<typename Derived>
class Base {
public:
void foo() {
static_cast<Derived*>(this)->foo_impl();
}
};
class Derived : public Base<Derived> {
public:
void foo_impl() {
// 実装
}
};
int main() {
Derived d;
d.foo();
}
上記の例ではBaseクラスがCRTPパターンで実装されており、「派生クラス自身が基底クラスと同じ型」であるような継承関係が作成されています。
これによりDerivedクラス内部からBaseクラスメソッドへアクセスすることが出来ます。

以上がC++におけるテンプレート応用例です。これらは非常に高度な技術ですが理解すれば非常に強力な機能となります。