備忘録です
//クラステンプレート
template<typename T = int> class A { };
//関数テンプレート
template<typename T = int> void f(T a) { }
//エイリアステンプレート(型の別名)
template<typename T = int> using B = A<T>; //typedefキーワードではなくusingキーワードを使用する
//変数テンプレート
template<typename T = int> T x; // C++14から(デフォルト引数はC++17から)
(どのサイトにも似たような説明が書かれているので、詳細は省きます)
テンプレートの型推論
推論がない場合、A<int>のように<型>が必要になります。推論は引数や初期値から型を特定します。
template<typename T> class A { //コンストラクタなし(C++17以前は推論なし,C++20からは初期値を使って推論可能)
public:
T m1;
};
template<typename T> class B { //コンストラクタ引数の型からT型を推論する
public:
T m1;
B(T a) { m1 = a; }
};
template<typename T> T f(T a) { return a; } //関数の引数からT型を推論する
int main()
{
A<int> a1; //メンバー変数m1はint型(推論なし)
A<char> a2; //メンバー変数m1はchar型(推論なし)
B b1(1); //メンバー変数m1はint型(引数から型を推論する)
B b2('1'); //メンバー変数m1はchar型(引数から型を推論する)
B<double> b3(1); //メンバー変数m1はdouble型(double型を指定(推論なし))
f(1); //引数はint型(引数から型を推論する)
f<double>(1); //引数はdouble型(推論なし)
//初期値から型を推論する(C++17から) ※1
B a3 { '1' }; //メンバー変数m1はchar型
B a4 = { '1' }; //メンバー変数m1はchar型
B a5 = { 2.0 }; //メンバー変数m1はdouble型
//(C++20から) ※2
A aa1 { 1 };
A aa2 = { '1' };
A aa3 = { 2.0 };
}
推論補助
template<typename T> class B {
public:
typedef T u_type;
};
namespace CCC {
template<class T1> class A {
public:
T1 m1;
template<class T2> A(T2 x) {}
};
//推論補助
//・Aクラスと同じ名前空間(例ではCCC)に記述する必要がある
//・Aクラスの外に記述する必要がある
template<class T3> A(T3) -> A<typename T3::u_type>;
} // namespace CCC
int main()
{
B<int> b;
CCC::A a(b); //1.コンストラクタ引数からテンプレートT2をB<int>型と推論する
//2.B<int>型を推論補助を使ってテンプレートT1はB<int>::u_type型(すなわちint型)と推論する
}
推論補助で何が嬉しいの?
コンテナの要素型を意識することなくテンプレートにできます(※1のURLに例があります)。
※1. クラステンプレートのテンプレート引数推論
※2. 集成体クラステンプレートのテンプレート引数推論
テンプレートの特殊化
//【クラステンプレートの特殊化】
//A.ジェネリックなクラステンプレート
template<typename T1, typename T2> class A { public : A(T1 a, T2 b) {;} };
//B.完全特殊化(bool型とfloat型に特殊化したクラステンプレートの定義)
// ジェネリックなクラステンプレートが無いとエラー
template<> class A<bool, float> { public :A(bool a, float b) {;}};
//C.部分特殊化(T1をbool型に特殊化したクラステンプレートの定義)
// ジェネリックなクラステンプレートが無いとエラー
template<typename T> class A<bool, T> { public : A(bool a, T b) {;} };
//D.部分特殊化(T2をbool型に特殊化したクラステンプレートの定義)
// ジェネリックなクラステンプレートが無いとエラー
template<typename T> class A<T, bool> { public : A(T a, bool b) {;} };
//【関数テンプレートの特殊化】
//A.ジェネリックな関数テンプレート
template<typename T1, typename T2> void f(T1 a, T2 b) { }
//B.完全特殊化(bool型とfloat型に特殊化した関数テンプレート)
// ジェネリックな関数テンプレートが無いとエラー
template<> void f(bool a, float b) { }
//C.部分特殊化ではない(Tのみジェネリックな関数テンプレート)
// 単独でもエラーにならない
template<typename T> void f(bool a, T b) { }
//D.部分特殊化ではない(Tのみジェネリックな関数テンプレート)
// 単独でもエラーにならない
template<typename T> void f(T a, bool b) { }
int main()
{
A<bool, double> a1(true, 2); //C.が呼ばれた(想像ではA.が呼ばれると思ってた)
//A<> a3(true, 2.0f); //コンパイルエラー
A a4(true, 2.0f); //B.が呼ばれる
A a2(true, 2.0); //C.が呼ばれる
A a5(true, 'a'); //C.が呼ばれる
A a6('a', true); //D.が呼ばれる
f<bool, double>(true,2); //A.が呼ばれる
f<>(true, 2.0f); //C.が呼ばれた(想像ではB.が呼ばれると思ってた)
f(true, 2.0f); //C.が呼ばれた(想像ではB.が呼ばれると思ってた)
f(true,2.0); //C.が呼ばれる
f(true, 'a'); //C.が呼ばれる
f('a', true); //D.が呼ばれる
//感じとして、呼び方の規則性が曖昧です。テンプレートをコメントアウトすることで
//自動で呼び先を変えてくれるのはいいのですが、意図しないものが呼ばれないように注意が必要です。
//この結果はバージョンやコンパイラーによって変わるのかもしれません。
}
//【エイリアステンプレートの特殊化】
template<typename T1, typename T2> class A { public : A(T1 a, T2 b) {;} };
template<typename T1, typename T2> using B = A<T1, T2>; //通常のエイリアステンプレート
//template<> using B = A<bool, float>; //完全特殊化はコンパイルエラー
//template<typename T> using B = A<bool, T>; //部分特殊化はコンパイルエラー
//【変数テンプレートの特殊化】
template<typename T1, typename T2> T1 x; //通常の変数テンプレート
template<> bool x<bool, float>; //完全特殊化(※1)
template<typename T> T* x<T, float>; //部分特殊化(C++23から?※1)
//【変数テンプレートの特殊化の実用的な使い方?】
template<typename T> T zero = 0; //C++23から※1
template<typename T> T* zero<T*> = nullptr; //C++23から※1
//エイリアステンプレートは特殊化できない
//変数テンプレートは特殊化できる(C++23から)
int main()
{
int a = zero<int>; //0
int* b = zero<int*>; //null
}
※1.実際にコンパイルしてみると g++ -std=c++17でコンパイルが通る
変数テンプレートの部分特殊化を許可
可変引数テンプレート
template <typename... Args> class A { };
template <typename... Args> void f(Args... args);
詳細は、可変引数テンプレート参照のこと
非型テンプレートパラメータ
テンプレートパラメータには普通は型を指定します。条件次第で値をパラメータにすることもできます。
class Z {};
template<typename T, T V> class A { T m1 = V; };
template<typename T> class B { T m1; };
template<int V> class C { decltype(V) m1 = V; };
//auto宣言(C++17)
template<auto V> class D { decltype(V) m1 = V; }; //decltype(V)でV値の型を取得
//クラス型を許可する(C++20)
template<Z V> class E { decltype(V) m1 = V; };
int main()
{
Z z;
A<int, 3> a; //型と値を指定できる
//B<3> b1; //コンパイルエラー
B<int> b2; //これだと値を指定できない
C<3> c1; //メンバー変数m1の型はint型(値は3)
C<true> c2; //メンバー変数m1の型はint型(値は1)
C<'c'> c3; //メンバー変数m1の型はint型(値は99)
//C<3.0> c4; //コンパイルエラー(浮動小数点数は渡せない)
D<3> d1; //C++17から<auto V>が使える
D<true> d2; //メンバー変数m1の型はbool型(値はtrue)
D<'c'> d3; //メンバー変数m1の型はchar型(値は'c')
//D<3.0> d4; //コンパイルエラー(浮動小数点数は渡せない)
E<z> e; //C++20から
}
非型テンプレートパラメータのauto宣言
非型テンプレートパラメータとしてクラス型を許可する
typenameキーワード
//コンパイラはtypenameとclassを区別しない
template<typename T> void f() {}
template<class T> void f() {}
昔はclassキーワードを使ってた。typenameキーワードは、C++98/03で規格化。
C++14以前のC++ではテンプレートテンプレートパラメータにtypenameキーワードを利用することができなかった。
長くなるので説明を割愛
・テンプレートテンプレートパラメータ
・任意の式によるSFINAE
・畳み込み式
・using宣言のパック展開
・コンセプト
・継承コンストラクタからのクラステンプレート引数の推論
さいごに
テンプレートは機能が豊富です。すべての機能を備忘録に書くには多すぎます。詳しく知りたい方はcpprefjp - C++日本語リファレンスをおすすめします。(言語機能のページに、C++11,C++14,C++17,C++20,C++23,C++26とバージョン毎にまとめられ、わかりやすく解説されていますがC++11より前の説明は見当たりません)