テンプレートの特殊化
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 66/104。
時として、汎用的なテンプレートがすべての型に対してうまく機能しないことがあります。例えば、Cスタイルの文字列を > で比較すると、実際のテキストではなくポインタのアドレスが比較されます。テンプレートの特殊化を使用すると、他のすべての型に対しては汎用バージョンを維持しつつ、特定の型に対してカスタム実装を提供できます。
完全特殊化は、特定の型に対してテンプレート全体を置き換えます。空のテンプレートパラメータリストを使用して宣言し、具体的な型を指定します:
// 基本テンプレート
template <typename T>
class Printer {
public:
void print(T value) {
std::cout << value << std::endl;
}
};
// const char* 用の完全特殊化
template <>
class Printer<const char*> {
public:
void print(const char* value) {
std::cout << "String: " << value << std::endl;
}
};
Printer<int> intPrinter;
intPrinter.print(42); // 出力: 42
Printer<const char*> strPrinter;
strPrinter.print("hello"); // 出力: String: hello関数テンプレートも特殊化することができます:
template <typename T>
bool isEqual(T a, T b) {
return a == b;
}
template <>
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0;
}
isEqual(5, 5); // 汎用バージョンを使用
isEqual("hi", "hi"); // 特殊化されたバージョンを使用コンパイラは常に最も具体的な一致を優先します。使用されている正確な型に対して特殊化が存在する場合、汎用テンプレートよりもそれが優先して選択されます。
チャレンジ
簡単型を認識するフォーマッタシステムを構築しましょう。これは、テンプレートの特殊化によって、特定の型に対して動作をカスタマイズしつつ、それ以外のすべての型に対しては汎用的なフォールバックを維持する方法を示すものです。
テンプレートコードを整理するために、2つのファイルを作成します:
Formatter.h: 表示用に値をフォーマットするFormatterというクラステンプレートを定義します。汎用テンプレートはあらゆる型で動作する必要がありますが、カスタム処理が必要な型については特殊化されたバージョンも作成します。プライマリ
Formatterテンプレートには以下を含める必要があります:- 型 T の値を受け取り、
Value: <value>と出力するformat()メソッド。
1 または 0 の代わりに人間が読めるテキストを出力する、
bool用の完全特殊化を作成します:format()メソッドは、Boolean: trueまたはBoolean: falseと出力する必要があります。
文字列を引用符で囲む、
const char*用の完全特殊化を作成します:format()メソッドは、String: "<value>"と出力する必要があります。
また、同じ型の2つの値を受け取り、それらをまとめて
Pair: [<first>, <second>]として出力するformatPairという関数テンプレートも作成します。各文字列を引用符で囲む
const char*用のformatPairの特殊化を作成します:Pair: ["<first>", "<second>"]- 型 T の値を受け取り、
main.cpp: 4つの入力(それぞれ別々の行)を読み取ります:- 整数
- double型の数値
- 文字列としての真偽値(
"true"または"false") - 文字列の値
適切な
Formatterオブジェクトを作成し、各型に対してformat()を呼び出すことで、フォーマッタの実演を行います:Formatter<int>を使用して整数をフォーマットするFormatter<double>を使用して double をフォーマットするFormatter<bool>を使用して真偽値をフォーマットする(まず文字列入力を bool に変換してください)Formatter<const char*>を使用して文字列をフォーマットする
次に、関数テンプレートの特殊化を実演します:
- 2つの整数 10 と 20 を指定して
formatPairを呼び出す - 2つのC言語スタイルの文字列
"hello"と"world"を指定してformatPairを呼び出す
例えば、入力が 42、3.14、true、Hello の場合:
Value: 42
Value: 3.14
Boolean: true
String: "Hello"
Pair: [10, 20]
Pair: ["hello", "world"]汎用テンプレートが整数と double を同じように処理する一方で、bool と const char* の特殊化されたバージョンがカスタムフォーマットを提供していることに注目してください。コンパイラは、使用する各型に対して最も具体的な一致を自動的に選択します。
チートシート
テンプレートの特殊化を使用すると、他のすべての型に対しては汎用的なバージョンを維持しつつ、特定の型に対してカスタム実装を提供できます。
完全特殊化の構文
空のテンプレートパラメータリスト template <> を使用して特殊化を宣言し、具体的な型を指定します。
// 基本テンプレート
template <typename T>
class Printer {
public:
void print(T value) {
std::cout << value << std::endl;
}
};
// const char* 用の完全特殊化
template <>
class Printer<const char*> {
public:
void print(const char* value) {
std::cout << "String: " << value << std::endl;
}
};
関数テンプレートの特殊化
関数テンプレートも特殊化できます。
template <typename T>
bool isEqual(T a, T b) {
return a == b;
}
template <>
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0;
}
コンパイラの選択
コンパイラは常に最も具体的な一致を優先します。使用されている正確な型に対して特殊化が存在する場合、汎用テンプレートよりもその特殊化が選択されます。
自分で試してみよう
#include <iostream>
#include <string>
#include "Formatter.h"
using namespace std;
int main() {
// 入力を読み込む
int intVal;
double doubleVal;
string boolStr;
string strVal;
cin >> intVal;
cin >> doubleVal;
cin >> boolStr;
cin >> strVal;
// 文字列を bool に変換する
bool boolVal = (boolStr == "true");
// TODO: Formatter<int> を作成し、整数をフォーマットする
// TODO: Formatter<double> を作成し、浮動小数点数をフォーマットする
// TODO: Formatter<bool> を作成し、真偽値をフォーマットする
// TODO: Formatter<const char*> を作成し、文字列をフォーマットする
// ヒント: const char* を取得するには strVal.c_str() を使用する
// TODO: 2つの整数 10 と 20 を指定して formatPair を呼び出す
// TODO: 2つのC言語形式の文字列 "hello" と "world" を指定して formatPair を呼び出す
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス