C++マニアック

テンプレート,関数,クラス,template,function,class

C++ の面白さは、テンプレートをきわめて、初めて理解できるといっていいでしょう。

  1. テンプレート関数とテンプレートクラス
  2. テンプレート関数
    1. テンプレート関数とは
    2. テンプレート関数がコンパイルできない場合
    3. テンプレート引数を増やしてみる
    4. テンプレート関数の異型
  3. テンプレートクラス
  4. テンプレート関連本

テンプレート関数とテンプレートクラス ページの先頭へ

まず認識すべきことは、テンプレートには大きく分けて次の二つがあるということです。

  1. テンプレート関数
  2. テンプレートクラス

意味はさておき、ここでは、まず、テンプレートには関数とクラスの二つがあるということを認識してください。

更に、上記 2 種類のテンプレートには、どちらにも、引数が変数型ではない異型があります。意味はさておき、異型があるということを認識してください。

以下、テンプレート関数、テンプレートクラスの順で解説します。異型については、それぞれの項目の後半で解説します。

テンプレート関数 ページの先頭へ

テンプレート関数は、グローバル関数またはクラスのメンバ関数として定義できます。グローバル関数としてよりもクラスのメンバ関数として使用する機会の方が多いでしょう。

テンプレート関数とは テンプレート関数 ページの先頭へ

テンプレート関数を説明するために、まず、テンプレートを使わない普通の関数のサンプルを見てみます。二つの整数 A、B を加算して返す関数 Add を考えます。

int Add(int iA, int iB) {
    return iA + iB;
}

上記の関数は、int 型の二つの引数 iA と iB を加算して返しますが、例えば double 型で同じ事をしようとすると、次のようになります。

double Add(double dA, double dB) {
    return dA + dB;
}

これらは、ほとんど同じなので、まとめて定義したくなります。そのようなときに、型である int や double をテンプレート引数(つまり変数)としてテンプレート関数を次のように定義できます。

template<typename T_n> // typename は class と書いても良い。
T_n Add(T_n tnA, T_n tnB) {
    return tnA + tnB;
}

テンプレートでない普通の関数と比較してみてください。1 行目は template<typename T_n> となっていますが、この部分が、これに続く関数がテンプレート関数であることを示しています。T_n は変数の型を表すテンプレート引数で、次の行からの関数定義部分の T_n を int や float に対応させる事によって、変数型の異なる関数まとめて定義したことになります。上記のようにテンプレート引数 T_n の前にはキーワード typename をおきます。typename の代わりに class と書いてもかまいません。また T_n は、この例の引数が数値(number)なので、数値を表すテンプレート引数という意味で、今回は T_n としましたが、例えば T や t1、abc など C++ の識別子として許される文字列であればどのようなものでもかまいません。ただし、意味が分かりやすいように命名すべきでしょう。私はテンプレート引数は T_ で始めることにしています。

上記で定義したテンプレート関数 Add を使ういろいろなパタンは、次のようになります。

int main() {
    double dValue  = Add(1.3, 2.4);     // @double Add(double, double) が使われます。
    int    iValue  = Add(1, 2);         // Aint Add(int, int) が使われます。
//  int    i       = Add(2.3, 1);       // BAdd(double, int) はないので、この行はコンパイルエラーになります。
    int    iValue2 = Add<int>(2.3, 1);  // Cint Add(int, int) が使われます。
    printf("dValue = %f, iValue = %d, iValue2 = %d\n",
        dValue, iValue, iValue2);       // 答えは 3.7 と 3 と 3 になります。
    return 0;
}

テンプレート関数のサンプル TemplateFunction1.cpp

@では、第一、第二引数の型が共に double であるため、コンパイラが使用すべき型の組み合わせを類推して double Add(double, double) が使われます。Aでは、同様に、第一、第二引数が共に int であるため int Add(int, int) が使われます。Bでは、引数が (double, int) なので、当該関数なしということで、コメントをはずすと、コンパイルエラーになります。Cでは、引数は (double, int) ですが、テンプレート引数を <int> で指定しているため、int Add(int, int) が使われます。

できれば、テンプレート引数は、常に Add<int> などと書いて、明示しておく方が曖昧さがなくてよいでしょう。テンプレート関数であるということも明らかになります。

テンプレート関数がコンパイルできない場合 テンプレート関数 ページの先頭へ

テンプレート関数のテンプレート引数は、変数型であれば何でも良いわけですが、テンプレート関数内部でその変数を使用する部分に、その変数型では使えないような使い方をした場合には、コンパイルエラーになります。例えば、次のテンプレート関数で考えて見ます。

template<typename T_n> // typename は class と書いても良い。
T_n Multiply(T_n tnA, T_n tnB) {
    return tnA * tnB;
}

上記テンプレート関数 Multiply では tnA と tnB を * 演算子で乗算しているので、変数型 T_n として、* 演算子が適用できないような型を指定した場合には、コンパイルできないことになります。例えば、string 型の変数には * 演算子を使えないので、次のようなコードはコンパイルエラーになります。

int main() {
    string strA = "Hello";
    string strB = "maniac";
    Multiply(strA, strB);
    return 0;
}

テンプレート関数がコンパイルできないサンプル CantComplie1.cpp

上記の main 関数で、Multiply でなく最初に出てきた Add を使えば、文字列をつなぐ事になり、これは、コンパイルが通ります。

int main() {
    string strA = "Hello";
    string strB = "maniac";
    string strC = Add(strA, strB);
    printf("%s\n", strC.c_str());
    return 0;
}

このように、テンプレート関数自体の定義には問題がなくても、使い方を誤ると、コンパイルできないことになります。逆に言えば、テンプレート関数のテンプレート引数には、想定された性能(例えば + 演算子が使えるという性能)があるということになります。テンプレート関数の引数として渡す変数をクラスオブジェクトとして作成する場合には、クラスの定義で、その性能を満たしてやる必要があるということです。例えば上記の例では、string クラスには + 演算子が定義されているので、Add の引数として渡すことが出来るわけです。

テンプレート引数を増やしてみる テンプレート関数 ページの先頭へ

次に、テンプレート引数の数を増やして、Add の戻り値と二つの引数それぞれに別の型を指定できるようにしてみます。ret、p1、p2 はそれぞれ、戻り値、引数 1、引数 2 という意味で命名してあります。

template<typename T_ret, typename T_p1, typename T_p2> // typename は class と書いても良い。
T_ret Add(T_p1 tp1A, T_p2 tp2B) {
    return tp1A + tp2B;
}

テンプレート関数のサンプル TemplateFunction2.cpp

上記を使用するサンプルは、次のようになります。

int main() {
//  int iValue0 = Add(2.6, 1.7);                        // コメントをはずすと、コンパイルエラーになります。
    int iValue1 = Add<int>(2.6, 1.7);                  // int Add(double, double) が使われます。
    int iValue2 = Add<double, float, int>(2.6, 1.7); // double Add(float, int) が使われます。
    printf("iValue1 = %d, iValue2 = %d\n", iValue1, iValue2);// 答えは 4 と 3 になります。
    return 0;
}

上記サンプルのように、テンプレート引数が複数あるときには、Add<double, float, int> などのように、テンプレート引数(型)を並べて記述します。

コメントアウトしてある行は、コメントを外すと、コンパイルエラーになります。これは、テンプレート引数 T_p1 と T_p2 は、関数のシグニチャから推定されますが、左辺の int iValue0 からは、int を推定してくれないからです。例えば、int 型を返す関数の戻り値を double 型の変数に代入するということは可能なので、左辺が int だからといって、テンプレート引数 T_ret を int とみなすことはしないようになっているわけです。そこで、最低でも iValue2 の行のように、最初の <int> だけは、指定してやる必要があるわけです。

テンプレート関数の異型 テンプレート関数 ページの先頭へ

テンプレート関数の最後として、引数が C++ の組み込み型であるような異型について解説します。サンプルは次のようになります。テンプレート引数として typename で変数型を指定するのではなく、C++ の組み込み型の変数、ここでは int iV、を使用します

template<int iV>
int Add(int iValue) {
    return iValue + iV;
}

テンプレート関数のサンプル TemplateFunction3.cpp

使用方法は、次のようになります。

int main() {
    int iValue = Add<3>(2);            // 2 + 3 が返されます。
    printf("iValue = %d\n", iValue);    // 答えは 5 になります。
    return 0;
}

この異型をどのように使用するかは自由ですが、例えば、整数 0, 1, 2, ... をテンプレート引数として与えて、同じ名前で異なる内容の関数を定義するというようなことも出来そうです。

テンプレートクラス ページの先頭へ

to bo continued ...