【サンプル付き】C++ 関数テンプレートの使い方を解説

[記事内には広告が含まれています]

こんにちは、現役エンジニアの inno_tech です。

C++の関数テンプレート(template)の使い方についてお困りでしょうか?今回は、関数テンプレートの使い方をサンプルコードを交えながら解説します。

本記事を読めば、関数テンプレートを使ってプログラミングできるようになり、冗長な型が違うだけの関数をなくすことができます。

もくじ

C++ 関数テンプレートとは?

関数テンプレートは、関数で使用する引数などの型をパラメータとして動的に変更を可能にする機能です。
(テンプレート関数 と呼ばれる場合もあります。)

従来(C言語)の場合、例えば足し算を行う関数は 引数がint型かdouble型かによって、それぞれ関数を作成する必要がありました。

/* 足し算(int型用) */
int sum_i(int a, int b){ 
    return (a + b); 
}

/* 足し算(double型用) */
double sum_d(double a, int b){ 
    return (a + b); 
}

この例ではint, doubleの2つだけど、全部の型に対応するとめっちゃ大変だ…。

一方、C++ 関数テンプレートを使えば、1つの関数を定義するだけでそれぞれの型に合った関数を自動的に生成できるようになります。

とても便利だから覚えていないと損する機能だね。

続いて、サンプルコードを交えて使い方を解説します。

サンプルコードを交えて解説

基本形 | 型が1種類の関数テンプレート

関数テンプレートの定義

テンプレート機能を使うには関数の前にtemplate <typename [テンプレート型名]>を付けます。
[テンプレート型名] の部分は自由に設定可能です。

それではサンプルコードです。[テンプレート型名]は T1 としました。

/* 合計値を返す(1つの型) */
template <typename T1>
T1 sum( T1 a, T1 b)
{
    return a + b; 
}

関数テンプレートを呼び出す側

テンプレート関数を呼び出すには、関数名の後ろに<使用したい型>を指定します。
つまり、関数名<[使用したい型名]>(引数1, 引数2... );という呼び出し方です。

それではサンプルコードです。int, double でそれぞれ同じsum() を呼び出す例です。

void TestTemplateFunc_001()
{
    printf("** test1-A: int型を指定 **\n" );
    int i_result = sum<int>(5, 6);
    std::cout << "i_result :" << i_result << std::endl;

    printf("** test1-B: double型を指定 **\n" );
    double d_result = sum<double>(5.5, 6.1);
    std::cout << "d_result :" << d_result << std::endl;

    return;
}

結果は [テンプレート型名]は T1<>で指定した型に置換されて処理されます。

** test1-A: int型を指定 **
i_result :11

** test1-B: double型を指定 **
d_result :11.6

この例の場合、int sum(int a, int b); double sum(double a, double b); の関数が内部的に生成されています。

関数テンプレートから各型の関数(インスタンス)が生成されるのはコンパイル時です。
呼び出した型名が最初に呼び出される時コンパイラがインスタンスを生成します。

派生形 | 型が複数種類のテンプレート関数

1つめの例では、T1という全て同じ型を指定していました。複数の型を使用したい場合はどうすればよいでしょうか?

関数テンプレートの定義

複数種類の関数テンプレートを定義するには、異なるテンプレート型名をカンマ区切りで指定します。
つまり、template <typename [テンプレート型名1], [テンプレート型名2], ...>という風にします。

サンプルコードでは、T1, T2というテンプレート型名にしました。

/* 合計値を返す(複数の型) */
template <typename T1, typename T2>
T2 sum_2( T1 a, T2 b)
{
    return a + b; 
}

関数テンプレートを呼び出す側

呼び出す側も同様に、カンマ区切りで使用したい型を指定します。サンプルコードがこちらです。


void TestTemplateFunc_002()
{
    printf("** test2-A: 複数の型を指定(int, double) **\n" );
    double result = sum_2<int, double>(5, 6.5);
    std::cout << "result :" << result << std::endl;

    return;
}

この例ではdouble sum_2(int a, double b); という関数が実行されたと同じ意味になります。したがって、double型(少数)で計算された結果が得られました。

** test2-A: 複数の型を指定(int, double) **
result :11.5

個別に指定できると汎用性が高まるね。

派生形 | 型の指定を省略して呼び出す(型推論)

ここまでの例では、関数テンプレートを呼び出す際に明示的に型を指定しましたが、コンパイラが推定可能な場合に限って型の指定を省略することも可能です。

関数テンプレートを呼び出す側型を省略

それではサンプルコードがこちらです。

void TestTemplateFunc_003()
{
    printf("** test3-A: 型の指定を省略 **\n" );
    double result_A = sum_2(5.5, 6.5);
    std::cout << "result :" << result_A << std::endl;

    printf("** test3-B: 型の初期値を指定(int, double) **\n" );
    double result_B = sum_2<int, double>(5.5, 6.5);
    std::cout << "result :" << result_B << std::endl;

    printf("** test3-C: 型を明示的に指定(double, double) **\n" );
    double result_C = sum_2<double, double>(5.5, 6.5);
    std::cout << "result :" << result_C << std::endl;

    return;
}

下のような結果になりました。

  • test3-A (テンプレート型を省略) | double sum_2(double a, double b); として型推論される(test3-Cと一致)
  • test3-B(明示的に int, double型を指定) | 第1引数の5.5がintにキャストされるため、結果は 5+6.5=11.5
** test3-A: 型の指定を省略 **
result :12
** test3-B: 型の初期値を指定(int, double) **
result :11.5
** test3-C: 型を明示的に指定(double, double) **
result :12

意図しない動作を避けるなら、基本的に型は明示した方がいいよ

関数テンプレートの特殊化

関数テンプレートで1つの関数定義で様々な型に対応できることがわかりました。

一方で「ある型の場合の場合だけは処理を変えたい」というような場合はどうすればよいでしょうか?

その答えは、「特殊化」というテクニックを使うことで実現ができます。

特殊化を行うには、テンプレート関数の<>に特定の型を指定した関数を定義します。

関数テンプレートの特殊化

サンプルコードでは、これまで説明した汎用テンプレート(引数がdouble以外) と 特殊化したテンプレート(引数がdouble用)の2種類を用意しました。

/* 汎用テンプレート */
template <typename T1>
inline void test_print(T1 a)
{
    std::cout << "汎用テンプレートです." << std::endl; 
    std::cout << a << std::endl; 
    return; 
}

/* テンプレートの特殊化 */
template <> 
inline void test_print<double>(double a)
{
    std::cout << "特殊化されています." << std::endl; 
    std::cout << a << std::endl; 
    return; 
}

ヘッダーファイル(.hpp)内に特殊化を実装した場合、複数のCPPファイルから特殊化した関数を参照すると多重定義のリンクエラーとなるのでサンプルコードではinline関数指定子をつけることで回避しています。

g++ -Wall -Iinclude -o bin/main.out obj/test_template.cpp.o obj/main.cpp.o  
/usr/bin/ld: obj/main.cpp.o: in function `void test_print<double>(double)':
main.cpp:(.text+0x0): multiple definition of `void test_print<double>(double)'; obj/test_template.cpp.o:test_template.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:38: bin/main.out] エラー 1

参照ファイルが1つの場合はエラーになりませんので環境に依ります。

特殊化した関数テンプレートを呼び出す側

呼び出す側で指定する型を変えて動作を確認します。

void TestTemplateFunc_004()
{
    printf("** test4-A: 汎用テンプレートを呼び出し **\n" );
    test_print<int>(5);

    printf("** test4-B: 特殊化テンプレートを呼び出し **\n" );
    test_print<double>(5.5);

    return;
}

結果はこのようになります。test4-Bではdouble型が指定されているので特殊化されたパターンの関数が呼び出されていることが確認できました。

** test4-A: 汎用テンプレートを呼び出し **
汎用テンプレートです.
5
** test4-B: 特殊化テンプレートを呼び出し **
特殊化されています.
5.5

以上で、C++の関数テンプレートの使い方の解説は終わりです。

まとめ

今回の関数テンプレート機能についてまとめます。

  • 関数テンプレート機能を使うことで、1つの関数定義で複数の型に対応可能
  • テンプレート化された関数を使用する場合は、関数名<[型名]>(引数1, 引数2... ); と指定する
  • 特殊化」により、特定の型の場合のみ別の動作とすることも可能

最後に、少し難易度が高めの本ですが、C++スキルのレベルアップを目指す方におすすめの本の紹介です。(中級者以上向け)

いかがだったでしょうか。
ほかにも役立つ情報が他のページにもあるかもしれません。
ご覧いただくかはあなた次第です!

この記事がイイねと思ったら、Twitterフォロー か  にほんブログ村のどちらかしてくれたら嬉しいよ!

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

コメント / ご要望

コメントする

goto
もくじ
閉じる