C++ Learning

第48回 関数テンプレート

「型を引数にする関数」がテンプレート。同じロジックを int / double / std::string など様々な型に対応させるために、関数を何度も書き直す必要がなくなります。std::maxstd::sortstd::swap などすべてテンプレートで実装されています。

このページで押さえること
✅ 最低限ここだけ覚える
  • template<class T> を先頭に書く
  • T を関数内で自由に使える
  • 呼び出すと型ごとに実体化される
  • 実装はヘッダに書く(後述)
⭐ 余裕があれば読む
  • 型推論 vs 明示指定
  • 非型テンプレート引数(整数値など)
  • 特殊化(特定の型だけ違う実装)
  • コンセプト (C++20)

1. まず触ってみる ― max のテンプレート化

C: 型ごとに書く冗長
int max_int(int a, int b); double max_double(double a, double b); // ... 型ごとに全部書く必要 // マクロで無理やり統一 #define MAX(a, b) ((a) > (b) ? (a) : (b)) // 型安全でない、副作用がある、等
C++: テンプレート推奨
template<class T> T max_v(T a, T b) { return a > b ? a : b; } max_v(3, 5); // T = int max_v(3.14, 2.71); // T = double max_v<std::string>("a", "b"); // T = std::string (明示)
▶ テンプレート実体化デモ
max_v( );
ここまでで覚えること(3 つ):
  • template<class T> を関数の前に書く
  • 関数内で T を任意の型として使える
  • 呼び出すとコンパイラが使われた型で実体化する

2. テンプレートの実体化(インスタンシエーション)

テンプレートは関数の設計図。実際の関数はコンパイラが使われた型ごとに生成します。

実体化のイメージ概念
// あなたが書いたテンプレート template<class T> T max_v(T a, T b) { return a > b ? a : b; } // コンパイラが生成するもの(概念): // max_v<int> を使うと… int max_v(int a, int b) { return a > b ? a : b; } // max_v<double> を使うと… double max_v(double a, double b) { return a > b ? a : b; } // max_v<std::string> を使うと… std::string max_v(std::string a, std::string b) { return a > b ? a : b; }

要するに「型ごとに手書きで書くのをコンパイラにやらせる」仕組み。実行時オーバーヘッドはゼロ(むしろ最適化が効きやすい)。

実装はヘッダに書く

テンプレートは使う場所でコンパイルされるので、ヘッダに実装も書きます(.cpp に分離できない)。

max_util.h全部ヘッダ
#pragma once template<class T> T max_v(T a, T b) { return a > b ? a : b; } // .cpp に定義を分けない(リンクエラーになる)

3. 型推論 vs 明示指定

型推論自動
template<class T> T identity(T x) { return x; } identity(42); // T = int 自動推論 identity(3.14); // T = double identity("hi"); // T = const char*
明示指定必要なとき
template<class T> T zero() { return T{}; } // 戻り値だけで T は決まらないので明示必要 zero<int>(); // 0 zero<double>(); // 0.0

複数の型パラメータ

複数型T, U
template<class T, class U> auto add(T a, U b) -> decltype(a + b) { return a + b; } add(1, 2.5); // T=int, U=double, 戻り値 double

4. 非型テンプレート引数

型だけでなく整数値もテンプレート引数にできます。配列サイズ等で便利。

N 個の平均非型引数
template<class T, size_t N> T average(const T (&arr)[N]) { T sum{}; for (size_t i = 0; i < N; ++i) sum += arr[i]; return sum / N; } int a[] = {1,2,3,4,5}; average(a); // N=5 が自動推論される
std::array も活用: std::array<int, 5>5 がまさに非型テンプレート引数。サイズが型の一部になります。
ここまでで関数テンプレートは OK
次章はクラステンプレート(std::vector や std::pair のような型)。

5. よくあるパターン

typename vs class

テンプレートパラメータを宣言するとき、class Ttypename T同じ意味。好みの問題で、型テンプレート引数にはどちらも使えます。

C++20 のコンセプト

「T は加算可能な型であるべき」「T はコピー可能であるべき」のような要求仕様を書けるのがコンセプト。エラーメッセージが大幅に改善され、テンプレート引数の型制約が明示できる。本サイトは C++17 基準なので、使える機能を紹介だけ。

広告スペース

確認クイズ

関数テンプレートを 4 問で確認。

Q1. 関数テンプレートの宣言として正しいのは?

generic<T> T f(T x);
template T f(T x);
template<class T> T f(T x);
function<T>(T x) -> T;
正しい構文は template<class T>(または typename T)を関数の前に置きます。

Q2. テンプレートの実装はどこに書く?

必ず .cpp 側
ヘッダ(.h)側
どちらでもよい
別のテンプレートファイル
テンプレートは使う場所でコンパイルされるので、定義も使用側から見える必要があります。通常はヘッダに全部書く(インライン扱い)。

Q3. template<class T> T max_v(T a, T b); に対し max_v(3, 2.5) は?

T=int に決まる
T=double に決まる
T=auto
コンパイルエラー(T が一意に決まらない)
両方の引数から同じ T を推論する際、int と double で矛盾するためコンパイルエラー。max_v<double>(3, 2.5) と明示するか、引数を揃える必要があります。

Q4. テンプレートの実行時オーバーヘッドは?

仮想関数と同程度
ハッシュ検索ぶん
基本ゼロ(コンパイル時に型ごとの関数が生成される)
コードサイズが倍になる
テンプレートはコンパイル時に展開される。実行時の分岐やテーブルルックアップはなく、手書きの型別関数と同等の性能。デメリットは実行時ではなくコンパイル時間とバイナリサイズへの影響。